diff --git a/clients/android/NewsBlur/AndroidManifest.xml b/clients/android/NewsBlur/AndroidManifest.xml index 394c66002..ec521ac7c 100644 --- a/clients/android/NewsBlur/AndroidManifest.xml +++ b/clients/android/NewsBlur/AndroidManifest.xml @@ -1,12 +1,6 @@ - - + package="com.newsblur"> diff --git a/clients/android/NewsBlur/build.gradle b/clients/android/NewsBlur/build.gradle index 719d475b1..1464b5bcf 100644 --- a/clients/android/NewsBlur/build.gradle +++ b/clients/android/NewsBlur/build.gradle @@ -8,7 +8,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:4.0.0' } } @@ -22,27 +22,29 @@ repositories { apply plugin: 'com.android.application' apply plugin: 'checkstyle' -apply plugin: 'findbugs' dependencies { - compile 'com.android.support:support-core-utils:27.1.1' - compile 'com.android.support:support-fragment:27.1.1' - compile 'com.android.support:support-core-ui:27.1.1' - compile 'com.jakewharton:butterknife:7.0.1' - compile 'com.squareup.okhttp3:okhttp:3.8.1' - compile 'com.google.code.gson:gson:2.8.2' - compile 'com.android.support:recyclerview-v7:27.1.1' + implementation 'com.android.support:support-core-utils:28.0.0' + implementation 'com.android.support:support-fragment:28.0.0' + implementation 'com.android.support:support-core-ui:28.0.0' + implementation 'com.squareup.okhttp3:okhttp:3.12.12' + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.android.support:recyclerview-v7:28.0.0' } android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 28 + defaultConfig { + applicationId "com.newsblur" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 166 + versionName "10.0b4" + } compileOptions.with { sourceCompatibility = JavaVersion.VERSION_1_7 } - - // force old processing behaviour that butterknife 7 depends on, until we can afford to upgrade - android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true + viewBinding.enabled = true sourceSets { main { diff --git a/clients/android/NewsBlur/proguard-project.txt b/clients/android/NewsBlur/proguard-project.txt index dd15510ff..65241cb88 100644 --- a/clients/android/NewsBlur/proguard-project.txt +++ b/clients/android/NewsBlur/proguard-project.txt @@ -11,16 +11,6 @@ -dontwarn okhttp3.** -dontnote okhttp3.** --keep class butterknife.** { *; } --dontwarn butterknife.internal.** --keep class **$$ViewBinder { *; } --keepclasseswithmembernames class * { - @butterknife.* ; -} --keepclasseswithmembernames class * { - @butterknife.* ; -} - # these two seem to confuse ProGuard, so force keep them -keep class com.newsblur.util.StateFilter { *; } -keep class com.newsblur.view.StateToggleButton$StateChangedListener { *; } diff --git a/clients/android/NewsBlur/res/drawable/ic_create_folder.xml b/clients/android/NewsBlur/res/drawable/ic_create_folder.xml new file mode 100644 index 000000000..6488d55be --- /dev/null +++ b/clients/android/NewsBlur/res/drawable/ic_create_folder.xml @@ -0,0 +1,10 @@ + + + diff --git a/clients/android/NewsBlur/res/layout/dialog_add_feed.xml b/clients/android/NewsBlur/res/layout/dialog_add_feed.xml new file mode 100644 index 000000000..802a16759 --- /dev/null +++ b/clients/android/NewsBlur/res/layout/dialog_add_feed.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clients/android/NewsBlur/res/layout/dialog_rename_feed.xml b/clients/android/NewsBlur/res/layout/dialog_rename.xml similarity index 50% rename from clients/android/NewsBlur/res/layout/dialog_rename_feed.xml rename to clients/android/NewsBlur/res/layout/dialog_rename.xml index 5e2f8d4e3..befad1511 100644 --- a/clients/android/NewsBlur/res/layout/dialog_rename_feed.xml +++ b/clients/android/NewsBlur/res/layout/dialog_rename.xml @@ -1,17 +1,19 @@ - + android:paddingRight="10dp"> + android:layout_marginBottom="10dp" + android:autofillHints="@null" + android:hint="@string/new_folder_name_hint" + android:inputType="textCapSentences" + android:singleLine="true" /> - + \ No newline at end of file diff --git a/clients/android/NewsBlur/res/layout/row_add_feed_folder.xml b/clients/android/NewsBlur/res/layout/row_add_feed_folder.xml new file mode 100644 index 000000000..6a9a3c633 --- /dev/null +++ b/clients/android/NewsBlur/res/layout/row_add_feed_folder.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/clients/android/NewsBlur/res/layout/row_saved_search_child.xml b/clients/android/NewsBlur/res/layout/row_saved_search_child.xml new file mode 100644 index 000000000..a7cc51b39 --- /dev/null +++ b/clients/android/NewsBlur/res/layout/row_saved_search_child.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + diff --git a/clients/android/NewsBlur/res/layout/row_saved_searches.xml b/clients/android/NewsBlur/res/layout/row_saved_searches.xml new file mode 100644 index 000000000..0cb997d10 --- /dev/null +++ b/clients/android/NewsBlur/res/layout/row_saved_searches.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/clients/android/NewsBlur/res/menu/context_feed.xml b/clients/android/NewsBlur/res/menu/context_feed.xml index d2f7df598..fb077077f 100644 --- a/clients/android/NewsBlur/res/menu/context_feed.xml +++ b/clients/android/NewsBlur/res/menu/context_feed.xml @@ -44,4 +44,7 @@ + + diff --git a/clients/android/NewsBlur/res/menu/context_folder.xml b/clients/android/NewsBlur/res/menu/context_folder.xml index 524746cf1..8f61c5d56 100644 --- a/clients/android/NewsBlur/res/menu/context_folder.xml +++ b/clients/android/NewsBlur/res/menu/context_folder.xml @@ -9,5 +9,11 @@ - + + + + + \ No newline at end of file diff --git a/clients/android/NewsBlur/res/menu/itemslist.xml b/clients/android/NewsBlur/res/menu/itemslist.xml index e7b65073f..4f85d2d62 100644 --- a/clients/android/NewsBlur/res/menu/itemslist.xml +++ b/clients/android/NewsBlur/res/menu/itemslist.xml @@ -72,4 +72,8 @@ + diff --git a/clients/android/NewsBlur/res/values/strings.xml b/clients/android/NewsBlur/res/values/strings.xml index c5ea7f311..d21b9e601 100644 --- a/clients/android/NewsBlur/res/values/strings.xml +++ b/clients/android/NewsBlur/res/values/strings.xml @@ -37,7 +37,8 @@ Choose Folders for Feed %s Rename Feed %s - + Rename Folder %s + ALL STORIES All Stories INFREQUENT SITE STORIES @@ -50,6 +51,7 @@ Read Stories SAVED STORIES Saved Stories + SAVED SEARCHES TOP LEVEL @@ -63,6 +65,7 @@ DELETE SHARE UPDATE COMMENT RENAME FEED + RENAME FOLDER SAVE REMOVE FROM SAVED @@ -132,6 +135,7 @@ Send story to… Mark feed as read Delete feed + Delete saved search Unfollow user Choose folders Notifications… @@ -156,6 +160,8 @@ Unmute feed Mute folder Unmute folder + Delete folder + Rename folder Insta-fetch stories Infrequent stories per month Intelligence trainer @@ -196,7 +202,11 @@ Add new feed Search Adding feed… + Please enter folder name + + Add new folder + Enter new folder name Could not add feed + Could not add folder Mark all as read Log out Send app feedback @@ -204,6 +214,8 @@ Email a bug report Theme… + Add new folder icon + Login as... Login As User @@ -234,6 +246,9 @@ %d SHARES Unknown User Delete feed \"%s\"? + Delete saved search %s? + Delete folder \"%s\" and unsubscribe from all feeds inside? + Save search \"%s\"? Unfollow \"%s\"? %s subscribers %d opens @@ -403,6 +418,7 @@ Storing text for %s stories... Storing %s images... Offline + Adding feed … Volume Key Navigation Off diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/AddFeedExternal.java b/clients/android/NewsBlur/src/com/newsblur/activity/AddFeedExternal.java index 094ae0318..c31952319 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/AddFeedExternal.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/AddFeedExternal.java @@ -5,32 +5,27 @@ import android.net.Uri; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.View; -import android.widget.TextView; - -import butterknife.ButterKnife; -import butterknife.Bind; import com.newsblur.R; +import com.newsblur.databinding.ActivityAddfeedexternalBinding; import com.newsblur.fragment.AddFeedFragment; import com.newsblur.util.UIUtils; import com.newsblur.util.ViewUtils; -import com.newsblur.view.ProgressThrobber; public class AddFeedExternal extends NbActivity implements AddFeedFragment.AddFeedProgressListener { - @Bind(R.id.loading_throb) ProgressThrobber progressView; - @Bind(R.id.progress_text) TextView progressText; + private ActivityAddfeedexternalBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + binding = ActivityAddfeedexternalBinding.inflate(getLayoutInflater()); getActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.activity_addfeedexternal); - ButterKnife.bind(this); + setContentView(binding.getRoot()); - progressView.setEnabled(!ViewUtils.isPowerSaveMode(this)); - progressView.setColors(UIUtils.getColor(this, R.color.refresh_1), + binding.loadingThrob.setEnabled(!ViewUtils.isPowerSaveMode(this)); + binding.loadingThrob.setColors(UIUtils.getColor(this, R.color.refresh_1), UIUtils.getColor(this, R.color.refresh_2), UIUtils.getColor(this, R.color.refresh_3), UIUtils.getColor(this, R.color.refresh_4)); @@ -48,9 +43,9 @@ public class AddFeedExternal extends NbActivity implements AddFeedFragment.AddFe public void addFeedStarted() { runOnUiThread(new Runnable() { public void run() { - progressText.setText(R.string.adding_feed_progress); - progressText.setVisibility(View.VISIBLE); - progressView.setVisibility(View.VISIBLE); + binding.progressText.setText(R.string.adding_feed_progress); + binding.progressText.setVisibility(View.VISIBLE); + binding.loadingThrob.setVisibility(View.VISIBLE); } }); } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/AllSharedStoriesItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/AllSharedStoriesItemsList.java index 6e9bf8393..a96dc7588 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/AllSharedStoriesItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/AllSharedStoriesItemsList.java @@ -14,4 +14,9 @@ public class AllSharedStoriesItemsList extends ItemsList { UIUtils.setCustomActionBar(this, R.drawable.ak_icon_blurblogs, getResources().getString(R.string.all_shared_stories_title)); } + @Override + String getSaveSearchFeedId() { + // doesn't have save search option + return null; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java index e9d5572bd..46486b2ce 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/AllStoriesItemsList.java @@ -24,4 +24,9 @@ public class AllStoriesItemsList extends ItemsList { UIUtils.startReadingActivity(fs, hash, this); } } + + @Override + String getSaveSearchFeedId() { + return "river:"; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java index 0c9e95a81..f3f66830e 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/FeedItemsList.java @@ -11,7 +11,7 @@ import com.newsblur.R; import com.newsblur.domain.Feed; import com.newsblur.fragment.DeleteFeedFragment; import com.newsblur.fragment.FeedIntelTrainerFragment; -import com.newsblur.fragment.RenameFeedFragment; +import com.newsblur.fragment.RenameDialogFragment; import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; import com.newsblur.util.UIUtils; @@ -79,8 +79,8 @@ public class FeedItemsList extends ItemsList { return true; } if (item.getItemId() == R.id.menu_rename_feed) { - RenameFeedFragment frag = RenameFeedFragment.newInstance(feed); - frag.show(getSupportFragmentManager(), RenameFeedFragment.class.getName()); + RenameDialogFragment frag = RenameDialogFragment.newInstance(feed); + frag.show(getSupportFragmentManager(), RenameDialogFragment.class.getName()); return true; // TODO: since this activity uses a feed object passed as an extra and doesn't query the DB, // the name change won't be reflected until the activity finishes. @@ -118,4 +118,8 @@ public class FeedItemsList extends ItemsList { return true; } + @Override + String getSaveSearchFeedId() { + return "feed:" + feed.feedId; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java index 7742c8561..78c925af3 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/FolderItemsList.java @@ -19,4 +19,8 @@ public class FolderItemsList extends ItemsList { UIUtils.setCustomActionBar(this, R.drawable.g_icn_folder_rss, folderName); } + @Override + String getSaveSearchFeedId() { + return "river:" + folderName; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/GlobalSharedStoriesItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/GlobalSharedStoriesItemsList.java index 1bd089d3d..4d8eb089a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/GlobalSharedStoriesItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/GlobalSharedStoriesItemsList.java @@ -14,4 +14,9 @@ public class GlobalSharedStoriesItemsList extends ItemsList { UIUtils.setCustomActionBar(this, R.drawable.ak_icon_global, getResources().getString(R.string.global_shared_stories_title)); } + @Override + String getSaveSearchFeedId() { + // doesn't have save search option + return null; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/InfrequentItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/InfrequentItemsList.java index ed2519ad8..712802c38 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/InfrequentItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/InfrequentItemsList.java @@ -37,4 +37,8 @@ public class InfrequentItemsList extends ItemsList implements InfrequentCutoffCh restartReadingSession(); } + @Override + String getSaveSearchFeedId() { + return "river:infrequent"; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java index 1e0e59806..ae6a4b32f 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/ItemsList.java @@ -10,17 +10,14 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnKeyListener; -import android.widget.EditText; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import butterknife.ButterKnife; -import butterknife.Bind; import com.newsblur.R; +import com.newsblur.databinding.ActivityItemslistBinding; import com.newsblur.fragment.ItemSetFragment; import com.newsblur.fragment.ReadFilterDialogFragment; +import com.newsblur.fragment.SaveSearchFragment; import com.newsblur.fragment.StoryOrderDialogFragment; import com.newsblur.fragment.TextSizeDialogFragment; import com.newsblur.service.NBSyncService; @@ -46,10 +43,9 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL private static final String READ_FILTER = "readFilter"; private static final String DEFAULT_FEED_VIEW = "defaultFeedView"; private static final String BUNDLE_ACTIVE_SEARCH_QUERY = "activeSearchQuery"; + private ActivityItemslistBinding binding; protected ItemSetFragment itemSetFragment; - @Bind(R.id.itemlist_sync_status) TextView overlayStatusText; - @Bind(R.id.itemlist_search_query) EditText searchQueryInput; protected StateFilter intelState; protected FeedSet fs; @@ -79,8 +75,8 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL getWindow().setBackgroundDrawableResource(android.R.color.transparent); - setContentView(R.layout.activity_itemslist); - ButterKnife.bind(this); + binding = ActivityItemslistBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); FragmentManager fragmentManager = getSupportFragmentManager(); itemSetFragment = (ItemSetFragment) fragmentManager.findFragmentByTag(ItemSetFragment.class.getName()); @@ -92,19 +88,23 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL transaction.commit(); } + String activeSearchQuery; if (bundle != null) { - String activeSearchQuery = bundle.getString(BUNDLE_ACTIVE_SEARCH_QUERY); - if (activeSearchQuery != null) { - searchQueryInput.setText(activeSearchQuery); - searchQueryInput.setVisibility(View.VISIBLE); - fs.setSearchQuery(activeSearchQuery); - } + activeSearchQuery = bundle.getString(BUNDLE_ACTIVE_SEARCH_QUERY); + } else { + activeSearchQuery = fs.getSearchQuery(); } - searchQueryInput.setOnKeyListener(new OnKeyListener() { + if (activeSearchQuery != null) { + binding.itemlistSearchQuery.setText(activeSearchQuery); + binding.itemlistSearchQuery.setVisibility(View.VISIBLE); + fs.setSearchQuery(activeSearchQuery); + } + + binding.itemlistSearchQuery.setOnKeyListener(new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK) && (event.getAction() == KeyEvent.ACTION_DOWN)) { - searchQueryInput.setVisibility(View.GONE); - searchQueryInput.setText(""); + binding.itemlistSearchQuery.setVisibility(View.GONE); + binding.itemlistSearchQuery.setText(""); checkSearchQuery(); return true; } @@ -120,8 +120,8 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - if (searchQueryInput != null) { - String q = searchQueryInput.getText().toString().trim(); + if (binding.itemlistSearchQuery != null) { + String q = binding.itemlistSearchQuery.getText().toString().trim(); if (q.length() > 0) { outState.putString(BUNDLE_ACTIVE_SEARCH_QUERY, q); } @@ -224,6 +224,12 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL menu.findItem(R.id.menu_theme_black).setChecked(true); } + if (!TextUtils.isEmpty(binding.itemlistSearchQuery.getText())) { + menu.findItem(R.id.menu_save_search).setVisible(true); + } else { + menu.findItem(R.id.menu_save_search).setVisible(false); + } + return true; } @@ -250,11 +256,11 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL textSize.show(getSupportFragmentManager(), TextSizeDialogFragment.class.getName()); return true; } else if (item.getItemId() == R.id.menu_search_stories) { - if (searchQueryInput.getVisibility() != View.VISIBLE) { - searchQueryInput.setVisibility(View.VISIBLE); - searchQueryInput.requestFocus(); + if (binding.itemlistSearchQuery.getVisibility() != View.VISIBLE) { + binding.itemlistSearchQuery.setVisibility(View.VISIBLE); + binding.itemlistSearchQuery.requestFocus(); } else { - searchQueryInput.setVisibility(View.GONE); + binding.itemlistSearchQuery.setVisibility(View.GONE); checkSearchQuery(); } } else if (item.getItemId() == R.id.menu_theme_light) { @@ -279,6 +285,14 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL PrefsUtils.updateStoryListStyle(this, fs, StoryListStyle.GRID_C); itemSetFragment.updateStyle(); } + if (item.getItemId() == R.id.menu_save_search) { + String feedId = getSaveSearchFeedId(); + if (feedId != null) { + String query = binding.itemlistSearchQuery.getText().toString(); + SaveSearchFragment frag = SaveSearchFragment.newInstance(feedId, query); + frag.show(getSupportFragmentManager(), SaveSearchFragment.class.getName()); + } + } return false; } @@ -315,23 +329,23 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL } private void updateStatusIndicators() { - if (overlayStatusText != null) { + if (binding.itemlistSyncStatus != null) { String syncStatus = NBSyncService.getSyncStatusMessage(this, true); if (syncStatus != null) { if (AppConstants.VERBOSE_LOG) { syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this); } - overlayStatusText.setText(syncStatus); - overlayStatusText.setVisibility(View.VISIBLE); + binding.itemlistSyncStatus.setText(syncStatus); + binding.itemlistSyncStatus.setVisibility(View.VISIBLE); } else { - overlayStatusText.setVisibility(View.GONE); + binding.itemlistSyncStatus.setVisibility(View.GONE); } } } private void checkSearchQuery() { String oldQuery = fs.getSearchQuery(); - String q = searchQueryInput.getText().toString().trim(); + String q = binding.itemlistSearchQuery.getText().toString().trim(); if (q.length() < 1) { q = null; } @@ -395,4 +409,6 @@ public abstract class ItemsList extends NbActivity implements StoryOrderChangedL */ overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right); } + + abstract String getSaveSearchFeedId(); } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java index 6e9c511af..1632669c1 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java @@ -17,19 +17,12 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnKeyListener; import android.widget.AbsListView; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; import com.newsblur.R; +import com.newsblur.databinding.ActivityMainBinding; import com.newsblur.fragment.FeedIntelligenceSelectorFragment; import com.newsblur.fragment.FolderListFragment; import com.newsblur.fragment.LoginAsDialogFragment; @@ -54,15 +47,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre private FragmentManager fragmentManager; private SwipeRefreshLayout swipeLayout; private boolean wasSwipeEnabled = false; - @Bind(R.id.main_sync_status) TextView overlayStatusText; - @Bind(R.id.empty_view_image) ImageView emptyViewImage; - @Bind(R.id.empty_view_text) TextView emptyViewText; - @Bind(R.id.main_menu_button) Button menuButton; - @Bind(R.id.main_user_image) ImageView userImage; - @Bind(R.id.main_user_name) TextView userName; - @Bind(R.id.main_unread_count_neut_text) TextView unreadCountNeutText; - @Bind(R.id.main_unread_count_posi_text) TextView unreadCountPosiText; - @Bind(R.id.feedlist_search_query) EditText searchQueryInput; + private ActivityMainBinding binding; @Override public void onCreate(Bundle savedInstanceState) { @@ -70,16 +55,15 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre super.onCreate(savedInstanceState); getWindow().setBackgroundDrawableResource(android.R.color.transparent); - - setContentView(R.layout.activity_main); - ButterKnife.bind(this); + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); getActionBar().hide(); // set the status bar to an generic loading message when the activity is first created so // that something is displayed while the service warms up - overlayStatusText.setText(R.string.loading); - overlayStatusText.setVisibility(View.VISIBLE); + binding.mainSyncStatus.setText(R.string.loading); + binding.mainSyncStatus.setVisibility(View.VISIBLE); swipeLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_container); swipeLayout.setColorSchemeResources(R.color.refresh_1, R.color.refresh_2, R.color.refresh_3, R.color.refresh_4); @@ -97,25 +81,25 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre Bitmap userPicture = PrefsUtils.getUserImage(this); if (userPicture != null) { userPicture = UIUtils.clipAndRound(userPicture, 5, false); - userImage.setImageBitmap(userPicture); + binding.mainUserImage.setImageBitmap(userPicture); } - userName.setText(PrefsUtils.getUserDetails(this).username); - searchQueryInput.setOnKeyListener(new OnKeyListener() { + binding.mainUserName.setText(PrefsUtils.getUserDetails(this).username); + binding.feedlistSearchQuery.setOnKeyListener(new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_BACK) && (event.getAction() == KeyEvent.ACTION_DOWN)) { - searchQueryInput.setVisibility(View.GONE); - searchQueryInput.setText(""); + binding.feedlistSearchQuery.setVisibility(View.GONE); + binding.feedlistSearchQuery.setText(""); checkSearchQuery(); return true; } if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { checkSearchQuery(); return true; - } + } return false; } }); - searchQueryInput.addTextChangedListener(new TextWatcher() { + binding.feedlistSearchQuery.addTextChangedListener(new TextWatcher() { public void onTextChanged(CharSequence s, int start, int before, int count) { checkSearchQuery(); } @@ -124,6 +108,31 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre }); FeedUtils.currentFolderName = null; + + binding.mainMenuButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickMenuButton(); + } + }); + binding.mainAddButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickAddButton(); + } + }); + binding.mainProfileButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickProfileButton(); + } + }); + binding.mainUserImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickUserButton(); + } + }); } @Override @@ -149,8 +158,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre } if (folderFeedList.getSearchQuery() != null) { - searchQueryInput.setText(folderFeedList.getSearchQuery()); - searchQueryInput.setVisibility(View.VISIBLE); + binding.feedlistSearchQuery.setText(folderFeedList.getSearchQuery()); + binding.feedlistSearchQuery.setVisibility(View.VISIBLE); } // triggerSync() might not actually do enough to push a UI update if background sync has been @@ -172,8 +181,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre if ( !( (state == StateFilter.ALL) || (state == StateFilter.SOME) || (state == StateFilter.BEST) ) ) { - searchQueryInput.setText(""); - searchQueryInput.setVisibility(View.GONE); + binding.feedlistSearchQuery.setText(""); + binding.feedlistSearchQuery.setVisibility(View.GONE); checkSearchQuery(); } @@ -201,8 +210,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre } public void updateUnreadCounts(int neutCount, int posiCount) { - unreadCountNeutText.setText(Integer.toString(neutCount)); - unreadCountPosiText.setText(Integer.toString(posiCount)); + binding.mainUnreadCountNeutText.setText(Integer.toString(neutCount)); + binding.mainUnreadCountPosiText.setText(Integer.toString(posiCount)); } /** @@ -213,22 +222,22 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre public void updateFeedCount(int feedCount) { if (feedCount < 1 ) { if (NBSyncService.isFeedCountSyncRunning() || (!folderFeedList.firstCursorSeenYet)) { - emptyViewImage.setVisibility(View.INVISIBLE); - emptyViewText.setVisibility(View.INVISIBLE); + binding.emptyViewImage.setVisibility(View.INVISIBLE); + binding.emptyViewText.setVisibility(View.INVISIBLE); } else { - emptyViewImage.setVisibility(View.VISIBLE); + binding.emptyViewImage.setVisibility(View.VISIBLE); if (folderFeedList.currentState == StateFilter.BEST) { - emptyViewText.setText(R.string.empty_list_view_no_focus_stories); + binding.emptyViewText.setText(R.string.empty_list_view_no_focus_stories); } else if (folderFeedList.currentState == StateFilter.SAVED) { - emptyViewText.setText(R.string.empty_list_view_no_saved_stories); + binding.emptyViewText.setText(R.string.empty_list_view_no_saved_stories); } else { - emptyViewText.setText(R.string.empty_list_view_no_unread_stories); + binding.emptyViewText.setText(R.string.empty_list_view_no_unread_stories); } - emptyViewText.setVisibility(View.VISIBLE); + binding.emptyViewText.setVisibility(View.VISIBLE); } } else { - emptyViewImage.setVisibility(View.INVISIBLE); - emptyViewText.setVisibility(View.INVISIBLE); + binding.emptyViewImage.setVisibility(View.INVISIBLE); + binding.emptyViewText.setVisibility(View.INVISIBLE); } } @@ -239,16 +248,16 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre swipeLayout.setRefreshing(false); } - if (overlayStatusText != null) { + if (binding.mainSyncStatus != null) { String syncStatus = NBSyncService.getSyncStatusMessage(this, false); if (syncStatus != null) { if (AppConstants.VERBOSE_LOG) { syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this); } - overlayStatusText.setText(syncStatus); - overlayStatusText.setVisibility(View.VISIBLE); + binding.mainSyncStatus.setText(syncStatus); + binding.mainSyncStatus.setVisibility(View.VISIBLE); } else { - overlayStatusText.setVisibility(View.GONE); + binding.mainSyncStatus.setVisibility(View.GONE); } } } @@ -260,8 +269,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre folderFeedList.clearRecents(); } - @OnClick(R.id.main_menu_button) void onClickMenuButton() { - PopupMenu pm = new PopupMenu(this, menuButton); + private void onClickMenuButton() { + PopupMenu pm = new PopupMenu(this, binding.mainMenuButton); Menu menu = pm.getMenu(); pm.getMenuInflater().inflate(R.menu.main, menu); @@ -308,12 +317,12 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre onRefresh(); return true; } else if (item.getItemId() == R.id.menu_search_feeds) { - if (searchQueryInput.getVisibility() != View.VISIBLE) { - searchQueryInput.setVisibility(View.VISIBLE); - searchQueryInput.requestFocus(); + if (binding.feedlistSearchQuery.getVisibility() != View.VISIBLE) { + binding.feedlistSearchQuery.setVisibility(View.VISIBLE); + binding.feedlistSearchQuery.requestFocus(); } else { - searchQueryInput.setText(""); - searchQueryInput.setVisibility(View.GONE); + binding.feedlistSearchQuery.setText(""); + binding.feedlistSearchQuery.setVisibility(View.GONE); checkSearchQuery(); } } else if (item.getItemId() == R.id.menu_add_feed) { @@ -364,17 +373,17 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre return false; } - @OnClick(R.id.main_add_button) void onClickAddButton() { + private void onClickAddButton() { Intent i = new Intent(this, SearchForFeeds.class); startActivity(i); } - @OnClick(R.id.main_profile_button) void onClickProfileButton() { + private void onClickProfileButton() { Intent i = new Intent(this, Profile.class); startActivity(i); } - @OnClick(R.id.main_user_image) void onClickUserButton() { + private void onClickUserButton() { Intent i = new Intent(this, Profile.class); startActivity(i); } @@ -404,7 +413,7 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre } private void checkSearchQuery() { - String q = searchQueryInput.getText().toString().trim(); + String q = binding.feedlistSearchQuery.getText().toString().trim(); if (q.length() < 1) { q = null; } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/ReadStoriesItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/ReadStoriesItemsList.java index 19210d75d..c6f07f663 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/ReadStoriesItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/ReadStoriesItemsList.java @@ -14,4 +14,8 @@ public class ReadStoriesItemsList extends ItemsList { UIUtils.setCustomActionBar(this, R.drawable.g_icn_unread_double, getResources().getString(R.string.read_stories_title)); } + @Override + String getSaveSearchFeedId() { + return "read"; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java b/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java index e020e351f..da6cb885a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java @@ -19,18 +19,14 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.Button; import android.widget.ProgressBar; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; import android.widget.Toast; -import butterknife.ButterKnife; -import butterknife.Bind; - import com.newsblur.R; import com.newsblur.database.ReadingAdapter; +import com.newsblur.databinding.ActivityReadingBinding; import com.newsblur.domain.Story; import com.newsblur.fragment.ReadingItemFragment; import com.newsblur.fragment.ReadingPagerFragment; @@ -77,16 +73,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener protected final Object STORIES_MUTEX = new Object(); protected Cursor stories; - @Bind(android.R.id.content) View contentView; // we use this a ton, so cache it - @Bind(R.id.reading_overlay_left) Button overlayLeft; - @Bind(R.id.reading_overlay_right) Button overlayRight; - @Bind(R.id.reading_overlay_progress) ProgressBar overlayProgress; - @Bind(R.id.reading_overlay_progress_right) ProgressBar overlayProgressRight; - @Bind(R.id.reading_overlay_progress_left) ProgressBar overlayProgressLeft; - @Bind(R.id.reading_overlay_text) Button overlayText; - @Bind(R.id.reading_overlay_send) Button overlaySend; - @Bind(R.id.reading_empty_view_text) View emptyViewText; - @Bind(R.id.reading_sync_status) TextView overlayStatusText; + private View contentView; // we use this a ton, so cache it ViewPager pager; ReadingPagerFragment readingFragment; @@ -109,6 +96,8 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener private VolumeKeyNavigation volumeKeyNavigation; + private ActivityReadingBinding binding; + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -119,8 +108,9 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener protected void onCreate(Bundle savedInstanceBundle) { super.onCreate(savedInstanceBundle); - setContentView(R.layout.activity_reading); - ButterKnife.bind(this); + binding = ActivityReadingBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + contentView = findViewById(android.R.id.content); try { fs = (FeedSet)getIntent().getSerializableExtra(EXTRA_FEEDSET); @@ -167,17 +157,17 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener this.pageHistory = new ArrayList(); - ViewUtils.setViewElevation(overlayLeft, OVERLAY_ELEVATION_DP); - ViewUtils.setViewElevation(overlayRight, OVERLAY_ELEVATION_DP); - ViewUtils.setViewElevation(overlayText, OVERLAY_ELEVATION_DP); - ViewUtils.setViewElevation(overlaySend, OVERLAY_ELEVATION_DP); - ViewUtils.setViewElevation(overlayProgress, OVERLAY_ELEVATION_DP); - ViewUtils.setViewElevation(overlayProgressLeft, OVERLAY_ELEVATION_DP); - ViewUtils.setViewElevation(overlayProgressRight, OVERLAY_ELEVATION_DP); + ViewUtils.setViewElevation(binding.readingOverlayLeft, OVERLAY_ELEVATION_DP); + ViewUtils.setViewElevation(binding.readingOverlayRight, OVERLAY_ELEVATION_DP); + ViewUtils.setViewElevation(binding.readingOverlayText, OVERLAY_ELEVATION_DP); + ViewUtils.setViewElevation(binding.readingOverlaySend, OVERLAY_ELEVATION_DP); + ViewUtils.setViewElevation(binding.readingOverlayProgress, OVERLAY_ELEVATION_DP); + ViewUtils.setViewElevation(binding.readingOverlayProgressLeft, OVERLAY_ELEVATION_DP); + ViewUtils.setViewElevation(binding.readingOverlayProgressRight, OVERLAY_ELEVATION_DP); // this likes to default to 'on' for some platforms - enableProgressCircle(overlayProgressLeft, false); - enableProgressCircle(overlayProgressRight, false); + enableProgressCircle(binding.readingOverlayProgressLeft, false); + enableProgressCircle(binding.readingOverlayProgressRight, false); FragmentManager fragmentManager = getSupportFragmentManager(); ReadingPagerFragment fragment = (ReadingPagerFragment) fragmentManager.findFragmentByTag(ReadingPagerFragment.class.getName()); @@ -312,7 +302,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener this.onPageSelected(position); // now that the pager is getting the right story, make it visible pager.setVisibility(View.VISIBLE); - emptyViewText.setVisibility(View.INVISIBLE); + binding.readingEmptyViewText.setVisibility(View.INVISIBLE); storyHash = null; return; } @@ -405,16 +395,16 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener } if ((updateType & UPDATE_STATUS) != 0) { enableMainProgress(NBSyncService.isFeedSetSyncing(this.fs, this)); - if (overlayStatusText != null) { + if (binding.readingSyncStatus != null) { String syncStatus = NBSyncService.getSyncStatusMessage(this, true); if (syncStatus != null) { if (AppConstants.VERBOSE_LOG) { syncStatus = syncStatus + UIUtils.getMemoryUsageDebug(this); } - overlayStatusText.setText(syncStatus); - overlayStatusText.setVisibility(View.VISIBLE); + binding.readingSyncStatus.setText(syncStatus); + binding.readingSyncStatus.setVisibility(View.VISIBLE); } else { - overlayStatusText.setVisibility(View.GONE); + binding.readingSyncStatus.setVisibility(View.GONE); } } } @@ -516,11 +506,11 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener final boolean _overflowExtras = overflowExtras; runOnUiThread(new Runnable() { public void run() { - UIUtils.setViewAlpha(overlayLeft, a, true); - UIUtils.setViewAlpha(overlayRight, a, true); - UIUtils.setViewAlpha(overlayProgress, a, true); - UIUtils.setViewAlpha(overlayText, a, true); - UIUtils.setViewAlpha(overlaySend, a, !_overflowExtras); + UIUtils.setViewAlpha(binding.readingOverlayLeft, a, true); + UIUtils.setViewAlpha(binding.readingOverlayRight, a, true); + UIUtils.setViewAlpha(binding.readingOverlayProgress, a, true); + UIUtils.setViewAlpha(binding.readingOverlayText, a, true); + UIUtils.setViewAlpha(binding.readingOverlaySend, a, !_overflowExtras); } }); } @@ -544,40 +534,40 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener if (currentUnreadCount > this.startingUnreadCount ) { this.startingUnreadCount = currentUnreadCount; } - this.overlayLeft.setEnabled(this.getLastReadPosition(false) != -1); - this.overlayRight.setText((currentUnreadCount > 0) ? R.string.overlay_next : R.string.overlay_done); + this.binding.readingOverlayLeft.setEnabled(this.getLastReadPosition(false) != -1); + this.binding.readingOverlayRight.setText((currentUnreadCount > 0) ? R.string.overlay_next : R.string.overlay_done); if (currentUnreadCount > 0) { - this.overlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRight, android.R.attr.background)); + this.binding.readingOverlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRight, android.R.attr.background)); } else { - this.overlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRightDone, android.R.attr.background)); + this.binding.readingOverlayRight.setBackgroundResource(UIUtils.getThemedResource(this, R.attr.selectorOverlayBackgroundRightDone, android.R.attr.background)); } if (this.startingUnreadCount == 0 ) { // sessions with no unreads just show a full progress bar - this.overlayProgress.setMax(1); - this.overlayProgress.setProgress(1); + this.binding.readingOverlayProgress.setMax(1); + this.binding.readingOverlayProgress.setProgress(1); } else { int unreadProgress = this.startingUnreadCount - currentUnreadCount; - this.overlayProgress.setMax(this.startingUnreadCount); - this.overlayProgress.setProgress(unreadProgress); + this.binding.readingOverlayProgress.setMax(this.startingUnreadCount); + this.binding.readingOverlayProgress.setProgress(unreadProgress); } - this.overlayProgress.invalidate(); + this.binding.readingOverlayProgress.invalidate(); invalidateOptionsMenu(); } private void updateOverlayText() { - if (overlayText == null) return; + if (binding.readingOverlayText == null) return; runOnUiThread(new Runnable() { public void run() { ReadingItemFragment item = getReadingFragment(); if (item == null) return; if (item.getSelectedViewMode() == DefaultFeedView.STORY) { - overlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundText, android.R.attr.background)); - overlayText.setText(R.string.overlay_text); + binding.readingOverlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundText, android.R.attr.background)); + binding.readingOverlayText.setText(R.string.overlay_text); } else { - overlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundStory, android.R.attr.background)); - overlayText.setText(R.string.overlay_story); + binding.readingOverlayText.setBackgroundResource(UIUtils.getThemedResource(Reading.this, R.attr.selectorOverlayBackgroundStory, android.R.attr.background)); + binding.readingOverlayText.setText(R.string.overlay_story); } } }); @@ -610,11 +600,11 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener } protected void enableMainProgress(boolean enabled) { - enableProgressCircle(overlayProgressRight, enabled); + enableProgressCircle(binding.readingOverlayProgressRight, enabled); } public void enableLeftProgressCircle(boolean enabled) { - enableProgressCircle(overlayProgressLeft, enabled); + enableProgressCircle(binding.readingOverlayProgressLeft, enabled); } private void enableProgressCircle(final ProgressBar view, final boolean enabled) { diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesItemsList.java index 48e65c883..1748bab80 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/SavedStoriesItemsList.java @@ -18,4 +18,13 @@ public class SavedStoriesItemsList extends ItemsList { UIUtils.setCustomActionBar(this, R.drawable.clock, title); } + @Override + String getSaveSearchFeedId() { + String feedId = "starred"; + String savedTag = fs.getSingleSavedTag(); + if (savedTag != null) { + feedId += ":" + savedTag; + } + return feedId; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedItemsList.java b/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedItemsList.java index a713043da..ff1211f12 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedItemsList.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/SocialFeedItemsList.java @@ -19,4 +19,8 @@ public class SocialFeedItemsList extends ItemsList { UIUtils.setCustomActionBar(this, socialFeed.photoUrl, socialFeed.feedTitle); } + @Override + String getSaveSearchFeedId() { + return "social:" + socialFeed.userId; + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/WidgetConfig.java b/clients/android/NewsBlur/src/com/newsblur/activity/WidgetConfig.java index 60f01a0c1..f35e42492 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/WidgetConfig.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/WidgetConfig.java @@ -9,10 +9,9 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.ExpandableListView; -import android.widget.TextView; import com.newsblur.R; +import com.newsblur.databinding.ActivityWidgetConfigBinding; import com.newsblur.domain.Feed; import com.newsblur.domain.Folder; import com.newsblur.util.FeedOrderFilter; @@ -31,28 +30,21 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import butterknife.Bind; -import butterknife.ButterKnife; - public class WidgetConfig extends NbActivity { - @Bind(R.id.list_view) - ExpandableListView listView; - @Bind(R.id.text_no_subscriptions) - TextView textNoSubscriptions; - private WidgetConfigAdapter adapter; private ArrayList feeds; private ArrayList folders; private Map feedMap = new HashMap<>(); private ArrayList folderNames = new ArrayList<>(); private ArrayList> folderChildren = new ArrayList<>(); + private ActivityWidgetConfigBinding binding; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_widget_config); - ButterKnife.bind(this); + binding = ActivityWidgetConfigBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); getActionBar().setDisplayHomeAsUpEnabled(true); setupList(); loadFeeds(); @@ -164,7 +156,7 @@ public class WidgetConfig extends NbActivity { private void setupList() { adapter = new WidgetConfigAdapter(this); - listView.setAdapter(adapter); + binding.listView.setAdapter(adapter); } private void loadFeeds() { @@ -288,7 +280,7 @@ public class WidgetConfig extends NbActivity { private void setAdapterData() { adapter.setData(this.folderNames, this.folderChildren, this.feeds); - listView.setVisibility(this.feeds.isEmpty() ? View.GONE : View.VISIBLE); - textNoSubscriptions.setVisibility(this.feeds.isEmpty() ? View.VISIBLE : View.GONE); + binding.listView.setVisibility(this.feeds.isEmpty() ? View.GONE : View.VISIBLE); + binding.textNoSubscriptions.setVisibility(this.feeds.isEmpty() ? View.VISIBLE : View.GONE); } } \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java index 24b087dd3..9d3d410b5 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabase.java @@ -27,6 +27,7 @@ public class BlurDatabase extends SQLiteOpenHelper { db.execSQL(DatabaseConstants.CLASSIFIER_SQL); db.execSQL(DatabaseConstants.SOCIALFEED_STORIES_SQL); db.execSQL(DatabaseConstants.STARREDCOUNTS_SQL); + db.execSQL(DatabaseConstants.SAVED_SEARCH_SQL); db.execSQL(DatabaseConstants.ACTION_SQL); db.execSQL(DatabaseConstants.NOTIFY_DISMISS_SQL); db.execSQL(DatabaseConstants.FEED_TAGS_SQL); @@ -49,6 +50,7 @@ public class BlurDatabase extends SQLiteOpenHelper { db.execSQL(drop + DatabaseConstants.CLASSIFIER_TABLE); db.execSQL(drop + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE); db.execSQL(drop + DatabaseConstants.STARREDCOUNTS_TABLE); + db.execSQL(drop + DatabaseConstants.SAVED_SEARCH_TABLE); db.execSQL(drop + DatabaseConstants.ACTION_TABLE); db.execSQL(drop + DatabaseConstants.NOTIFY_DISMISS_TABLE); db.execSQL(drop + DatabaseConstants.FEED_TAGS_TABLE); diff --git a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java index 9808af613..71ec7799e 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/BlurDatabaseHelper.java @@ -195,6 +195,13 @@ public class BlurDatabaseHelper { synchronized (RW_MUTEX) {dbRW.delete(DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE, DatabaseConstants.SOCIALFEED_STORY_USER_ID + " = ?", selArgs);} } + public void deleteSavedSearch(String feedId, String query) { + String q = "DELETE FROM " + DatabaseConstants.SAVED_SEARCH_TABLE + + " WHERE " + DatabaseConstants.SAVED_SEARCH_FEED_ID + " = '" + feedId + "'" + + " AND " + DatabaseConstants.SAVED_SEARCH_QUERY + " = '" + query + "'"; + synchronized (RW_MUTEX) {dbRW.execSQL(q);} + } + public Feed getFeed(String feedId) { Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE, null, DatabaseConstants.FEED_ID + " = ?", new String[] {feedId}, null, null, null); Feed result = null; @@ -237,7 +244,8 @@ public class BlurDatabaseHelper { public void setFeedsFolders(List folderValues, List feedValues, List socialFeedValues, - List starredCountValues) { + List starredCountValues, + List savedSearchValues) { synchronized (RW_MUTEX) { dbRW.beginTransaction(); try { @@ -248,10 +256,12 @@ public class BlurDatabaseHelper { dbRW.delete(DatabaseConstants.COMMENT_TABLE, null, null); dbRW.delete(DatabaseConstants.REPLY_TABLE, null, null); dbRW.delete(DatabaseConstants.STARREDCOUNTS_TABLE, null, null); + dbRW.delete(DatabaseConstants.SAVED_SEARCH_TABLE, null, null); bulkInsertValuesExtSync(DatabaseConstants.FOLDER_TABLE, folderValues); bulkInsertValuesExtSync(DatabaseConstants.FEED_TABLE, feedValues); bulkInsertValuesExtSync(DatabaseConstants.SOCIALFEED_TABLE, socialFeedValues); bulkInsertValuesExtSync(DatabaseConstants.STARREDCOUNTS_TABLE, starredCountValues); + bulkInsertValuesExtSync(DatabaseConstants.SAVED_SEARCH_TABLE, savedSearchValues); dbRW.setTransactionSuccessful(); } finally { dbRW.endTransaction(); @@ -1033,6 +1043,17 @@ public class BlurDatabaseHelper { return result; } + @Nullable + public StarredCount getStarredFeedByTag(String tag) { + Cursor c = dbRO.query(DatabaseConstants.STARREDCOUNTS_TABLE, null, DatabaseConstants.STARREDCOUNTS_TAG + " = ?", new String[] {tag}, null, null, null); + StarredCount result = null; + while (c.moveToNext()) { + result = StarredCount.fromCursor(c); + } + c.close(); + return result; + } + public List getFolders() { Cursor c = getFoldersCursor(null); List folders = new ArrayList(c.getCount()); @@ -1069,11 +1090,21 @@ public class BlurDatabaseHelper { }; } + public Loader getSavedSearchLoader() { + return new QueryCursorLoader(context) { + protected Cursor createCursor() {return getSavedSearchCursor(cancellationSignal);} + }; + } + private Cursor getSavedStoryCountsCursor(CancellationSignal cancellationSignal) { Cursor c = query(false, DatabaseConstants.STARREDCOUNTS_TABLE, null, null, null, null, null, null, null, cancellationSignal); return c; } + private Cursor getSavedSearchCursor(CancellationSignal cancellationSignal) { + return query(false, DatabaseConstants.SAVED_SEARCH_TABLE, null, null, null, null, null, null, null, cancellationSignal); + } + public Cursor getNotifyFocusStoriesCursor() { return rawQuery(DatabaseConstants.NOTIFY_FOCUS_STORY_QUERY, null, null); } diff --git a/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java b/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java index 5b5a8639f..517ffda2a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/DatabaseConstants.java @@ -148,6 +148,13 @@ public class DatabaseConstants { public static final String STARREDCOUNTS_TAG = "tag"; public static final String STARREDCOUNTS_FEEDID = "feed_id"; + public static final String SAVED_SEARCH_TABLE = "saved_search"; + public static final String SAVED_SEARCH_FEED_TITLE = "saved_search_title"; + public static final String SAVED_SEARCH_FAVICON = "saved_search_favicon"; + public static final String SAVED_SEARCH_ADDRESS = "saved_search_address"; + public static final String SAVED_SEARCH_QUERY = "saved_search_query"; + public static final String SAVED_SEARCH_FEED_ID = "saved_search_feed_id"; + public static final String NOTIFY_DISMISS_TABLE = "notify_dimiss"; public static final String NOTIFY_DISMISS_STORY_HASH = "story_hash"; public static final String NOTIFY_DISMISS_TIME = "time"; @@ -185,7 +192,7 @@ public class DatabaseConstants { FEED_FAVICON_BORDER + TEXT + ", " + FEED_LINK + TEXT + ", " + FEED_SUBSCRIBERS + TEXT + ", " + - FEED_TITLE + TEXT + ", " + + FEED_TITLE + TEXT + ", " + FEED_OPENS + INTEGER + ", " + FEED_AVERAGE_STORIES_PER_MONTH + INTEGER + ", " + FEED_LAST_STORY_DATE + TEXT + ", " + @@ -303,6 +310,14 @@ public class DatabaseConstants { STARREDCOUNTS_FEEDID + TEXT + ")"; + static final String SAVED_SEARCH_SQL = "CREATE TABLE " + SAVED_SEARCH_TABLE + " (" + + SAVED_SEARCH_FEED_TITLE + TEXT + ", " + + SAVED_SEARCH_FAVICON + TEXT + ", " + + SAVED_SEARCH_ADDRESS + TEXT + ", " + + SAVED_SEARCH_QUERY + TEXT + ", " + + SAVED_SEARCH_FEED_ID + + ")"; + static final String NOTIFY_DISMISS_SQL = "CREATE TABLE " + NOTIFY_DISMISS_TABLE + " (" + NOTIFY_DISMISS_STORY_HASH + TEXT + ", " + NOTIFY_DISMISS_TIME + INTEGER + " NOT NULL " + diff --git a/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java b/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java index ef584528b..4e980e6c0 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/FolderListAdapter.java @@ -27,6 +27,7 @@ import android.widget.TextView; import com.newsblur.R; import com.newsblur.domain.Feed; import com.newsblur.domain.Folder; +import com.newsblur.domain.SavedSearch; import com.newsblur.domain.StarredCount; import com.newsblur.domain.SocialFeed; import com.newsblur.util.AppConstants; @@ -34,14 +35,15 @@ import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; import com.newsblur.util.PrefsUtils; import com.newsblur.util.StateFilter; +import com.newsblur.util.UIUtils; /** * Custom adapter to display a nested folder/feed list in an ExpandableListView. */ public class FolderListAdapter extends BaseExpandableListAdapter { - private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, INFREQUENT_STORIES, ALL_STORIES, FOLDER, READ_STORIES, SAVED_STORIES } - private enum ChildType { SOCIAL_FEED, FEED, SAVED_BY_TAG } + private enum GroupType { GLOBAL_SHARED_STORIES, ALL_SHARED_STORIES, INFREQUENT_STORIES, ALL_STORIES, FOLDER, READ_STORIES, SAVED_SEARCHES, SAVED_STORIES } + private enum ChildType { SOCIAL_FEED, FEED, SAVED_BY_TAG, SAVED_SEARCH } // The following keys are used to mark the position of the special meta-folders within // the folders array. Since the ExpandableListView doesn't handle collapsing of views @@ -56,6 +58,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter { private static final String INFREQUENT_SITE_STORIES_GROUP_KEY = "INFREQUENT_SITE_STORIES_GROUP_KEY"; private static final String READ_STORIES_GROUP_KEY = "READ_STORIES_GROUP_KEY"; private static final String SAVED_STORIES_GROUP_KEY = "SAVED_STORIES_GROUP_KEY"; + private static final String SAVED_SEARCHES_GROUP_KEY = "SAVED_SEARCHES_GROUP_KEY"; private final static float defaultTextSize_childName = 14; private final static float defaultTextSize_groupName = 13; @@ -101,6 +104,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter { /** Starred story sets in display order. */ private List starredCountsByTag = Collections.emptyList(); + /** Saved Searches */ + private List savedSearches = Collections.emptyList(); private int savedStoriesTotalCount; @@ -162,6 +167,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter { if (v == null) v = inflater.inflate(R.layout.row_infrequent_stories, null, false); } else if (isRowReadStories(groupPosition)) { if (v == null) v = inflater.inflate(R.layout.row_read_stories, null, false); + } else if (isRowSavedSearches(groupPosition)) { + if (v == null) v = inflater.inflate(R.layout.row_saved_searches, null, false); } else if (isRowSavedStories(groupPosition)) { if (v == null) v = inflater.inflate(R.layout.row_saved_stories, null, false); TextView savedSum = ((TextView) v.findViewById(R.id.row_foldersum)); @@ -268,7 +275,15 @@ public class FolderListAdapter extends BaseExpandableListAdapter { TextView savedCounter =((TextView) v.findViewById(R.id.row_saved_tag_sum)); savedCounter.setText(Integer.toString(checkNegativeUnreads(sc.count))); savedCounter.setTextSize(textSize * defaultTextSize_count); - } else { + } else if (isRowSavedSearches(groupPosition)) { + if (v == null) v = inflater.inflate(R.layout.row_saved_search_child, parent, false); + SavedSearch ss = savedSearches.get(childPosition); + TextView nameView = v.findViewById(R.id.row_saved_search_title); + nameView.setText(UIUtils.fromHtml(ss.feedTitle)); + ImageView iconView = v.findViewById(R.id.row_saved_search_icon); + FeedUtils.iconLoader.preCheck(ss.faviconUrl, iconView); + FeedUtils.iconLoader.displayImage(ss.faviconUrl, iconView, 0 , false); + } else { if (v == null) v = inflater.inflate(R.layout.row_feed, parent, false); Feed f = activeFolderChildren.get(groupPosition).get(childPosition); TextView nameView =((TextView) v.findViewById(R.id.row_feedname)); @@ -392,6 +407,11 @@ public class FolderListAdapter extends BaseExpandableListAdapter { return folder.name; } + public Folder getGroupFolder(int groupPosition) { + String flatFolderName = activeFolderNames.get(groupPosition); + return flatFolders.get(flatFolderName); + } + @Override public synchronized int getGroupCount() { if (activeFolderNames == null) return 0; @@ -409,7 +429,9 @@ public class FolderListAdapter extends BaseExpandableListAdapter { return socialFeedsActive.size(); } else if (isRowSavedStories(groupPosition)) { return starredCountsByTag.size(); - } else { + } else if (isRowSavedSearches(groupPosition)) { + return savedSearches.size(); + } else { return activeFolderChildren.get(groupPosition).size(); } } @@ -421,6 +443,9 @@ public class FolderListAdapter extends BaseExpandableListAdapter { return FeedSet.singleSocialFeed(socialFeed.userId, socialFeed.username); } else if (isRowSavedStories(groupPosition)) { return FeedSet.singleSavedTag(starredCountsByTag.get(childPosition).tag); + } else if (isRowSavedSearches(groupPosition)) { + SavedSearch savedSearch = savedSearches.get(childPosition); + return FeedSet.singleSavedSearch(savedSearch.feedId, savedSearch.query); } else { Feed feed = activeFolderChildren.get(groupPosition).get(childPosition); FeedSet fs = FeedSet.singleFeed(feed.feedId); @@ -465,6 +490,10 @@ public class FolderListAdapter extends BaseExpandableListAdapter { return SAVED_STORIES_GROUP_KEY.equals(activeFolderNames.get(groupPosition)); } + public boolean isRowSavedSearches(int groupPosition) { + return SAVED_SEARCHES_GROUP_KEY.equals(activeFolderNames.get(groupPosition)); + } + /** * Determines if the row at the specified position is last of the special rows, under which * un-foldered "root level" feeds are created as children. These feeds are not in any folder, @@ -564,6 +593,17 @@ public class FolderListAdapter extends BaseExpandableListAdapter { recountFeeds(); notifyDataSetChanged(); } + + public synchronized void setSavedSearchesCursor(Cursor cursor) { + if (!cursor.isBeforeFirst()) return; + savedSearches = new ArrayList<>(); + while (cursor.moveToNext()) { + SavedSearch savedSearch = SavedSearch.fromCursor(cursor); + savedSearches.add(savedSearch); + } + Collections.sort(savedSearches, SavedSearch.SavedSearchComparatorByTitle); + notifyDataSetChanged(); + } private void recountFeeds() { if ((folders == null) || (feeds == null)) return; @@ -619,6 +659,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter { } // add the always-present (if enabled) special rows/folders that got at the bottom of the list addSpecialRow(READ_STORIES_GROUP_KEY); + addSpecialRow(SAVED_SEARCHES_GROUP_KEY); addSpecialRow(SAVED_STORIES_GROUP_KEY); recountChildren(); } @@ -728,6 +769,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter { totalNeutCount = 0; totalPosCount = 0; + safeClear(savedSearches); safeClear(starredCountsByTag); safeClear(closedFolders); @@ -755,6 +797,11 @@ public class FolderListAdapter extends BaseExpandableListAdapter { return socialFeedsActive.get(childPosition); } + /** Get the cached SavedSearch object at the given saved search list location. */ + public SavedSearch getSavedSearch(int childPosition) { + return savedSearches.get(childPosition); + } + public synchronized void changeState(StateFilter state) { currentState = state; lastFeedViewedId = null; // clear when changing modes @@ -804,6 +851,8 @@ public class FolderListAdapter extends BaseExpandableListAdapter { return GroupType.INFREQUENT_STORIES.ordinal(); } else if (isRowReadStories(groupPosition)) { return GroupType.READ_STORIES.ordinal(); + } else if (isRowSavedSearches(groupPosition)) { + return GroupType.SAVED_SEARCHES.ordinal(); } else if (isRowSavedStories(groupPosition)) { return GroupType.SAVED_STORIES.ordinal(); } else { @@ -817,7 +866,9 @@ public class FolderListAdapter extends BaseExpandableListAdapter { return ChildType.SOCIAL_FEED.ordinal(); } else if (isRowSavedStories(groupPosition)) { return ChildType.SAVED_BY_TAG.ordinal(); - } else { + } else if (isRowSavedSearches(groupPosition)) { + return ChildType.SAVED_SEARCH.ordinal(); + } else { return ChildType.FEED.ordinal(); } } diff --git a/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java b/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java index a2ef07712..dec95911a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java @@ -1,6 +1,5 @@ package com.newsblur.database; -import android.content.Intent; import android.database.Cursor; import android.graphics.Color; import android.graphics.Typeface; @@ -22,18 +21,13 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; -import butterknife.Bind; -import butterknife.ButterKnife; - import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.newsblur.R; import com.newsblur.activity.FeedItemsList; -import com.newsblur.activity.ItemsList; import com.newsblur.activity.NbActivity; import com.newsblur.domain.Story; import com.newsblur.domain.UserDetails; @@ -313,16 +307,16 @@ public class StoryViewAdapter extends RecyclerView.Adapter orphanFeedIds) { feedIds.removeAll(orphanFeedIds); } + + @Nullable + public String getFirstParentName() { + String folderParentName = null; + if (!parents.isEmpty()) { + folderParentName = parents.get(0); + } + return folderParentName; + } @Override public boolean equals(Object otherFolder) { diff --git a/clients/android/NewsBlur/src/com/newsblur/domain/SavedSearch.java b/clients/android/NewsBlur/src/com/newsblur/domain/SavedSearch.java new file mode 100644 index 000000000..e138031c3 --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/domain/SavedSearch.java @@ -0,0 +1,122 @@ +package com.newsblur.domain; + +import android.content.ContentValues; +import android.database.Cursor; + +import com.google.gson.annotations.SerializedName; +import com.newsblur.database.DatabaseConstants; +import com.newsblur.util.FeedSet; +import com.newsblur.util.FeedUtils; + +import java.util.Comparator; + +public class SavedSearch { + + @SerializedName("query") + public String query; + + @SerializedName("feed_id") + public String feedId; + + @SerializedName("feed_address") + public String feedAddress; + + public String feedTitle; + public String faviconUrl; + + public ContentValues getValues() { + ContentValues values = new ContentValues(); + String feedTitle = "\"" + query + "\" in " + getFeedTitle() + ""; + values.put(DatabaseConstants.SAVED_SEARCH_FEED_TITLE, feedTitle); + values.put(DatabaseConstants.SAVED_SEARCH_FAVICON, getFaviconUrl()); + values.put(DatabaseConstants.SAVED_SEARCH_ADDRESS, feedAddress); + values.put(DatabaseConstants.SAVED_SEARCH_QUERY, query); + values.put(DatabaseConstants.SAVED_SEARCH_FEED_ID, feedId); + return values; + } + + public static SavedSearch fromCursor(Cursor cursor) { + if (cursor.isBeforeFirst()) { + cursor.moveToFirst(); + } + SavedSearch savedSearch = new SavedSearch(); + savedSearch.feedTitle = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_FEED_TITLE)); + savedSearch.faviconUrl = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_FAVICON)); + savedSearch.feedAddress = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_ADDRESS)); + savedSearch.query = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_QUERY)); + savedSearch.feedId = cursor.getString(cursor.getColumnIndex(DatabaseConstants.SAVED_SEARCH_FEED_ID)); + return savedSearch; + } + + private String getFeedTitle() { + String feedTitle = null; + + if (feedId.equals("river:")) { + feedTitle = "All Site Stories"; + } else if (feedId.equals("river:infrequent")) { + feedTitle = "Infrequent Site Stories"; + } else if (feedId.startsWith("river:")) { + String folderName = feedId.replace("river:", ""); + FeedSet fs = FeedUtils.feedSetFromFolderName(folderName); + feedTitle = fs.getFolderName(); + } else if (feedId.equals("read")) { + feedTitle = "Read Stories"; + } else if (feedId.startsWith("starred")) { + feedTitle = "Saved Stories"; + String tag = feedId.replace("starred:", ""); + StarredCount starredFeed = FeedUtils.getStarredFeedByTag(tag); + if (starredFeed != null) { + String tagSlug = tag.replace(" ", "-"); + if (starredFeed.tag.equals(tag) || starredFeed.tag.equals(tagSlug)) { + feedTitle = feedTitle + " - " + starredFeed.tag; + } + } + } else if (feedId.startsWith("feed:")) { + Feed feed = FeedUtils.getFeed(feedId.replace("feed:", "")); + if (feed == null) return null; + feedTitle = feed.title; + } else if (feedId.startsWith("social:")) { + Feed feed = FeedUtils.getFeed(feedId.replace("social:", "")); + if (feed == null) return null; + feedTitle = feed.title; + } + + return feedTitle; + } + + private String getFaviconUrl() { + String url = null; + if (feedId.equals("river:") || feedId.equals("river:infrequent")) { + url = "https://newsblur.com/media/img/icons/circular/ak-icon-allstories.png"; + } else if (feedId.startsWith("river:")) { + url = "https://newsblur.com/media/img/icons/circular/g_icn_folder.png"; + } else if (feedId.equals("read")) { + url = "https://newsblur.com/media/img/icons/circular/g_icn_unread.png"; + } else if (feedId.equals("starred")) { + url = "https://newsblur.com/media/img/icons/circular/clock.png"; + } else if (feedId.startsWith("starred:")) { + url = "https://newsblur.com/media/img/reader/tag.png"; + } else if (feedId.startsWith("feed:")) { + Feed feed = FeedUtils.getFeed(feedId.replace("feed:", "")); + if (feed != null) { + url = feed.faviconUrl; + } + } else if (feedId.startsWith("social:")) { + Feed feed = FeedUtils.getFeed(feedId.replace("social:", "")); + if (feed != null) { + url = feed.faviconUrl; + } + } + if (url == null) { + url = "https://newsblur.com/media/img/icons/circular/g_icn_search_black.png"; + } + return url; + } + + public final static Comparator SavedSearchComparatorByTitle = new Comparator() { + @Override + public int compare(SavedSearch ss1, SavedSearch ss2) { + return String.CASE_INSENSITIVE_ORDER.compare(ss1.feedTitle, ss2.feedTitle); + } + }; +} diff --git a/clients/android/NewsBlur/src/com/newsblur/domain/Story.java b/clients/android/NewsBlur/src/com/newsblur/domain/Story.java index c897fd0f7..d62d876a3 100644 --- a/clients/android/NewsBlur/src/com/newsblur/domain/Story.java +++ b/clients/android/NewsBlur/src/com/newsblur/domain/Story.java @@ -2,6 +2,7 @@ package com.newsblur.domain; import java.io.Serializable; import java.util.Arrays; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,6 +83,12 @@ public class Story implements Serializable { @SerializedName("story_hash") public String storyHash; + @SerializedName("secure_image_urls") + public Map secureImageUrls; + + @SerializedName("secure_image_thumbnails") + public Map secureImageThumbnails; + // NOTE: this is parsed and saved to the DB, but is *not* generally un-thawed when stories are fetched back from the DB @SerializedName("image_urls") public String[] imageUrls; @@ -310,10 +317,13 @@ public class Story implements Serializable { return YT_THUMB_PRE + ytUrl + YT_THUMB_POST; } - if ((story.imageUrls != null) && (story.imageUrls.length > 0)) { - return story.imageUrls[0]; + if (story.imageUrls != null && story.imageUrls.length > 0) { + String thumbnail = story.imageUrls[0]; + if (thumbnail.startsWith("http://") && story.secureImageThumbnails != null && story.secureImageThumbnails.containsKey(thumbnail)){ + thumbnail = story.secureImageThumbnails.get(thumbnail); + } + return thumbnail; } - return null; } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/AddFeedFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/AddFeedFragment.java index 3f1f2fb03..b022f00e5 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/AddFeedFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/AddFeedFragment.java @@ -3,83 +3,217 @@ package com.newsblur.fragment; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.widget.Toast; import com.newsblur.R; import com.newsblur.activity.Main; +import com.newsblur.databinding.DialogAddFeedBinding; +import com.newsblur.databinding.RowAddFeedFolderBinding; +import com.newsblur.domain.Folder; import com.newsblur.network.APIManager; import com.newsblur.network.domain.AddFeedResponse; +import com.newsblur.network.domain.NewsBlurResponse; import com.newsblur.service.NBSyncService; +import com.newsblur.util.AppConstants; +import com.newsblur.util.FeedUtils; import com.newsblur.util.UIUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class AddFeedFragment extends DialogFragment { - private static final String FEED_URI = "feed_url"; - private static final String FEED_NAME = "feed_name"; + private static final String FEED_URI = "feed_url"; + private static final String FEED_NAME = "feed_name"; + private DialogAddFeedBinding binding; - public static AddFeedFragment newInstance(String feedUri, String feedName) { - AddFeedFragment frag = new AddFeedFragment(); - Bundle args = new Bundle(); - args.putString(FEED_URI, feedUri); - args.putString(FEED_NAME, feedName); - frag.setArguments(args); - return frag; - } + public static AddFeedFragment newInstance(String feedUri, String feedName) { + AddFeedFragment frag = new AddFeedFragment(); + Bundle args = new Bundle(); + args.putString(FEED_URI, feedUri); + args.putString(FEED_NAME, feedName); + frag.setArguments(args); + return frag; + } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final String addFeedString = getResources().getString(R.string.add_feed_message); final Activity activity = getActivity(); final APIManager apiManager = new APIManager(activity); - final Intent intent = new Intent(activity, Main.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + LayoutInflater inflater = LayoutInflater.from(activity); + View v = inflater.inflate(R.layout.dialog_add_feed, null); + binding = DialogAddFeedBinding.bind(v); AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setMessage(String.format(addFeedString, getArguments().getString(FEED_NAME))); - builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - - new AsyncTask() { - @Override - protected AddFeedResponse doInBackground(Void... arg) { - ((AddFeedProgressListener) activity).addFeedStarted(); - return apiManager.addFeed(getArguments().getString(FEED_URI)); - } + builder.setTitle("Choose folder for " + getArguments().getString(FEED_NAME)); + builder.setView(v); - @Override - protected void onPostExecute(AddFeedResponse result) { - if (!result.isError()) { - // trigger a sync when we return to Main so that the new feed will show up - NBSyncService.forceFeedsFolders(); - intent.putExtra(Main.EXTRA_FORCE_SHOW_FEED_ID, result.feed.feedId); - } else { - UIUtils.safeToast(activity, R.string.add_feed_error, Toast.LENGTH_SHORT); - } - activity.startActivity(intent); - activity.finish(); - AddFeedFragment.this.dismiss(); - }; - }.execute(); - } - }); - builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() { + AddFeedAdapter adapter = new AddFeedAdapter(new OnFolderClickListener() { @Override - public void onClick(DialogInterface dialogInterface, int i) { - AddFeedFragment.this.dismiss(); - activity.startActivity(intent); - activity.finish(); + public void onItemClick(Folder folder) { + addFeed(activity, apiManager, folder.name); } }); + + binding.textAddFolderTitle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (binding.containerAddFolder.getVisibility() == View.GONE) { + binding.containerAddFolder.setVisibility(View.VISIBLE); + } else { + binding.containerAddFolder.setVisibility(View.GONE); + } + } + }); + binding.icCreateFolder.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (binding.inputFolderName.getText().length() == 0) { + Toast.makeText(activity, R.string.add_folder_name, Toast.LENGTH_SHORT).show(); + } else { + addFeedToNewFolder(activity, apiManager, binding.inputFolderName.getText().toString()); + } + } + }); + + binding.recyclerViewFolders.addItemDecoration(new DividerItemDecoration(activity, LinearLayoutManager.VERTICAL)); + binding.recyclerViewFolders.setAdapter(adapter); + adapter.setFolders(FeedUtils.dbHelper.getFolders()); return builder.create(); } - public interface AddFeedProgressListener { - public abstract void addFeedStarted(); + private void addFeedToNewFolder(final Activity activity, final APIManager apiManager, final String folderName) { + binding.icCreateFolder.setVisibility(View.GONE); + binding.progressBar.setVisibility(View.VISIBLE); + binding.inputFolderName.setEnabled(false); + + new AsyncTask() { + @Override + protected NewsBlurResponse doInBackground(Void... voids) { + return apiManager.addFolder(folderName); + } + + @Override + protected void onPostExecute(NewsBlurResponse newsBlurResponse) { + super.onPostExecute(newsBlurResponse); + binding.inputFolderName.setEnabled(true); + + if (!newsBlurResponse.isError()) { + binding.containerAddFolder.setVisibility(View.GONE); + binding.inputFolderName.getText().clear(); + addFeed(activity, apiManager, folderName); + } else { + UIUtils.safeToast(activity, R.string.add_folder_error, Toast.LENGTH_SHORT); + } + } + }.execute(); } -} + + private void addFeed(final Activity activity, final APIManager apiManager, @Nullable final String folderName) { + binding.textSyncStatus.setVisibility(View.VISIBLE); + new AsyncTask() { + @Override + protected AddFeedResponse doInBackground(Void... voids) { + ((AddFeedProgressListener) activity).addFeedStarted(); + String feedUrl = getArguments().getString(FEED_URI); + return apiManager.addFeed(feedUrl, folderName); + } + + @Override + protected void onPostExecute(AddFeedResponse result) { + super.onPostExecute(result); + binding.textSyncStatus.setVisibility(View.GONE); + final Intent intent = new Intent(activity, Main.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + if (!result.isError()) { + // trigger a sync when we return to Main so that the new feed will show up + NBSyncService.forceFeedsFolders(); + intent.putExtra(Main.EXTRA_FORCE_SHOW_FEED_ID, result.feed.feedId); + } else { + UIUtils.safeToast(activity, R.string.add_feed_error, Toast.LENGTH_SHORT); + } + activity.startActivity(intent); + activity.finish(); + AddFeedFragment.this.dismiss(); + } + }.execute(); + } + + private static class AddFeedAdapter extends RecyclerView.Adapter { + + AddFeedAdapter(OnFolderClickListener listener) { + this.listener = listener; + } + + private final List folders = new ArrayList<>(); + private OnFolderClickListener listener; + + @NonNull + @Override + public AddFeedAdapter.FolderViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) { + View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_add_feed_folder, viewGroup, false); + return new FolderViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull AddFeedAdapter.FolderViewHolder viewHolder, int position) { + final Folder folder = folders.get(position); + if (folder.name.equals(AppConstants.ROOT_FOLDER)) { + viewHolder.binding.textFolderTitle.setText(R.string.top_level); + } else { + viewHolder.binding.textFolderTitle.setText(folder.flatName()); + } + viewHolder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onItemClick(folder); + } + }); + } + + @Override + public int getItemCount() { + return folders.size(); + } + + public void setFolders(List folders) { + Collections.sort(folders, Folder.FolderComparator); + this.folders.clear(); + this.folders.addAll(folders); + this.notifyDataSetChanged(); + } + + static class FolderViewHolder extends RecyclerView.ViewHolder { + + public RowAddFeedFolderBinding binding; + + public FolderViewHolder(@NonNull View itemView) { + super(itemView); + binding = RowAddFeedFolderBinding.bind(itemView); + } + } + } + + public interface AddFeedProgressListener { + void addFeedStarted(); + } + + public interface OnFolderClickListener { + void onItemClick(Folder folder); + } +} \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ChooseFoldersFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ChooseFoldersFragment.java index 59d6aa33c..7744b7fff 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ChooseFoldersFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ChooseFoldersFragment.java @@ -19,12 +19,9 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.ListAdapter; -import android.widget.ListView; - -import butterknife.ButterKnife; -import butterknife.Bind; import com.newsblur.R; +import com.newsblur.databinding.DialogChoosefoldersBinding; import com.newsblur.domain.Feed; import com.newsblur.domain.Folder; import com.newsblur.util.FeedUtils; @@ -33,8 +30,6 @@ public class ChooseFoldersFragment extends DialogFragment { private Feed feed; - @Bind(R.id.choose_folders_list) ListView listView; - public static ChooseFoldersFragment newInstance(Feed feed) { ChooseFoldersFragment fragment = new ChooseFoldersFragment(); Bundle args = new Bundle(); @@ -62,7 +57,7 @@ public class ChooseFoldersFragment extends DialogFragment { final Activity activity = getActivity(); LayoutInflater inflater = LayoutInflater.from(activity); View v = inflater.inflate(R.layout.dialog_choosefolders, null); - ButterKnife.bind(this, v); + DialogChoosefoldersBinding binding = DialogChoosefoldersBinding.bind(v); AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(String.format(getResources().getString(R.string.title_choose_folders), feed.title)); @@ -107,7 +102,7 @@ public class ChooseFoldersFragment extends DialogFragment { return v; } }; - listView.setAdapter(adapter); + binding.chooseFoldersList.setAdapter(adapter); Dialog dialog = builder.create(); dialog.getWindow().getAttributes().gravity = Gravity.BOTTOM; diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java index bce62fe33..7f6a2847f 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java @@ -4,9 +4,11 @@ import com.newsblur.R; import com.newsblur.activity.ItemsList; import com.newsblur.activity.NbActivity; import com.newsblur.domain.Feed; +import com.newsblur.domain.SavedSearch; import com.newsblur.domain.SocialFeed; import com.newsblur.network.APIManager; import com.newsblur.util.FeedUtils; +import com.newsblur.util.UIUtils; import android.app.Activity; import android.app.AlertDialog; @@ -22,7 +24,9 @@ public class DeleteFeedFragment extends DialogFragment { private static final String FOLDER_NAME = "folder_name"; private static final String NORMAL_FEED = "normal"; private static final String SOCIAL_FEED = "social"; - + private static final String SAVED_SEARCH_FEED = "saved_search"; + private static final String QUERY = "query"; + public static DeleteFeedFragment newInstance(Feed feed, String folderName) { DeleteFeedFragment frag = new DeleteFeedFragment(); Bundle args = new Bundle(); @@ -44,11 +48,25 @@ public class DeleteFeedFragment extends DialogFragment { return frag; } + public static DeleteFeedFragment newInstance(SavedSearch savedSearch) { + DeleteFeedFragment frag = new DeleteFeedFragment(); + Bundle args = new Bundle(); + args.putString(FEED_TYPE, SAVED_SEARCH_FEED); + args.putString(FEED_ID, savedSearch.feedId); + args.putString(FEED_NAME, savedSearch.feedTitle); + args.putString(QUERY, savedSearch.query); + frag.setArguments(args); + return frag; + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); if (getArguments().getString(FEED_TYPE).equals(NORMAL_FEED)) { builder.setMessage(String.format(getResources().getString(R.string.delete_feed_message), getArguments().getString(FEED_NAME))); + } else if (getArguments().getString(FEED_TYPE).equals(SAVED_SEARCH_FEED)) { + String message = String.format(getResources().getString(R.string.delete_saved_search_message), getArguments().getString(FEED_NAME)); + builder.setMessage(UIUtils.fromHtml(message)); } else { builder.setMessage(String.format(getResources().getString(R.string.unfollow_message), getArguments().getString(FEED_NAME))); } @@ -57,6 +75,8 @@ public class DeleteFeedFragment extends DialogFragment { public void onClick(DialogInterface dialogInterface, int i) { if (getArguments().getString(FEED_TYPE).equals(NORMAL_FEED)) { FeedUtils.deleteFeed(getArguments().getString(FEED_ID), getArguments().getString(FOLDER_NAME), getActivity(), new APIManager(getActivity())); + } else if (getArguments().getString(FEED_TYPE).equals(SAVED_SEARCH_FEED)) { + FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity(), new APIManager(getActivity())); } else { FeedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), getActivity(), new APIManager(getActivity())); } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFolderFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFolderFragment.java new file mode 100644 index 000000000..ae604c270 --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFolderFragment.java @@ -0,0 +1,57 @@ +package com.newsblur.fragment; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.text.TextUtils; + +import com.newsblur.R; +import com.newsblur.network.APIManager; +import com.newsblur.util.AppConstants; +import com.newsblur.util.FeedUtils; + +public class DeleteFolderFragment extends DialogFragment { + + private static final String FOLDER_NAME = "folder_name"; + private static final String FOLDER_PARENT = "folder_parent"; + + public static DeleteFolderFragment newInstance(String folderName, String folderParent) { + DeleteFolderFragment frag = new DeleteFolderFragment(); + Bundle args = new Bundle(); + args.putString(FOLDER_NAME, folderName); + args.putString(FOLDER_PARENT, folderParent); + frag.setArguments(args); + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final String folderName = getArguments().getString(FOLDER_NAME); + final String folderParent = getArguments().getString(FOLDER_PARENT); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(getResources().getString(R.string.delete_folder_message, folderName)); + builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + String inFolder = ""; + if (!TextUtils.isEmpty(folderParent) && !folderParent.equals(AppConstants.ROOT_FOLDER)) { + inFolder = folderParent; + } + FeedUtils.deleteFolder(folderName, inFolder, getActivity(), new APIManager(getActivity())); + DeleteFolderFragment.this.dismiss(); + } + }); + builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + DeleteFolderFragment.this.dismiss(); + } + }); + return builder.create(); + } +} \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/FeedIntelTrainerFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/FeedIntelTrainerFragment.java index 905af830f..451bdfada 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/FeedIntelTrainerFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/FeedIntelTrainerFragment.java @@ -12,13 +12,10 @@ import android.support.v4.app.DialogFragment; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.widget.LinearLayout; import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.Bind; - import com.newsblur.R; +import com.newsblur.databinding.DialogTrainfeedBinding; import com.newsblur.domain.Classifier; import com.newsblur.domain.Feed; import com.newsblur.util.FeedSet; @@ -30,14 +27,7 @@ public class FeedIntelTrainerFragment extends DialogFragment { private Feed feed; private FeedSet fs; private Classifier classifier; - - @Bind(R.id.intel_title_header) TextView headerTitles; - @Bind(R.id.intel_tag_header) TextView headerTags; - @Bind(R.id.intel_author_header) TextView headerAuthor; - @Bind(R.id.existing_title_intel_container) LinearLayout titleRowsContainer; - @Bind(R.id.existing_tag_intel_container) LinearLayout tagRowsContainer; - @Bind(R.id.existing_author_intel_container) LinearLayout authorRowsContainer; - @Bind(R.id.existing_feed_intel_container) LinearLayout feedRowsContainer; + private DialogTrainfeedBinding binding; public static FeedIntelTrainerFragment newInstance(Feed feed, FeedSet fs) { FeedIntelTrainerFragment fragment = new FeedIntelTrainerFragment(); @@ -58,7 +48,7 @@ public class FeedIntelTrainerFragment extends DialogFragment { final Activity activity = getActivity(); LayoutInflater inflater = LayoutInflater.from(activity); View v = inflater.inflate(R.layout.dialog_trainfeed, null); - ButterKnife.bind(this, v); + binding = DialogTrainfeedBinding.bind(v); // display known title classifiers for (Map.Entry rule : classifier.title.entrySet()) { @@ -66,9 +56,9 @@ public class FeedIntelTrainerFragment extends DialogFragment { TextView label = (TextView) row.findViewById(R.id.intel_row_label); label.setText(rule.getKey()); UIUtils.setupIntelDialogRow(row, classifier.title, rule.getKey()); - titleRowsContainer.addView(row); + binding.existingTitleIntelContainer.addView(row); } - if (classifier.title.size() < 1) headerTitles.setVisibility(View.GONE); + if (classifier.title.size() < 1) binding.intelTitleHeader.setVisibility(View.GONE); // get the list of suggested tags List allTags = FeedUtils.dbHelper.getTagsForFeed(feed.feedId); @@ -83,9 +73,9 @@ public class FeedIntelTrainerFragment extends DialogFragment { TextView label = (TextView) row.findViewById(R.id.intel_row_label); label.setText(tag); UIUtils.setupIntelDialogRow(row, classifier.tags, tag); - tagRowsContainer.addView(row); + binding.existingTagIntelContainer.addView(row); } - if (allTags.size() < 1) headerTags.setVisibility(View.GONE); + if (allTags.size() < 1) binding.intelTagHeader.setVisibility(View.GONE); // get the list of suggested authors List allAuthors = FeedUtils.dbHelper.getAuthorsForFeed(feed.feedId); @@ -100,16 +90,16 @@ public class FeedIntelTrainerFragment extends DialogFragment { TextView labelAuthor = (TextView) rowAuthor.findViewById(R.id.intel_row_label); labelAuthor.setText(author); UIUtils.setupIntelDialogRow(rowAuthor, classifier.authors, author); - authorRowsContainer.addView(rowAuthor); + binding.existingAuthorIntelContainer.addView(rowAuthor); } - if (allAuthors.size() < 1) headerAuthor.setVisibility(View.GONE); + if (allAuthors.size() < 1) binding.intelAuthorHeader.setVisibility(View.GONE); // for feel-level intel, the label is the title and the intel identifier is the feed ID View rowFeed = inflater.inflate(R.layout.include_intel_row, null); TextView labelFeed = (TextView) rowFeed.findViewById(R.id.intel_row_label); labelFeed.setText(feed.title); UIUtils.setupIntelDialogRow(rowFeed, classifier.feeds, feed.feedId); - feedRowsContainer.addView(rowFeed); + binding.existingFeedIntelContainer.addView(rowFeed); AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.feed_intel_dialog_title); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java index de47213bc..3c13048f6 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/FolderListFragment.java @@ -26,9 +26,6 @@ import android.widget.ExpandableListView.OnGroupClickListener; import android.widget.ExpandableListView.OnGroupCollapseListener; import android.widget.ExpandableListView.OnGroupExpandListener; -import butterknife.ButterKnife; -import butterknife.Bind; - import com.newsblur.R; import com.newsblur.activity.AllSharedStoriesItemsList; import com.newsblur.activity.AllStoriesItemsList; @@ -43,7 +40,10 @@ import com.newsblur.activity.ReadStoriesItemsList; import com.newsblur.activity.SavedStoriesItemsList; import com.newsblur.activity.SocialFeedItemsList; import com.newsblur.database.FolderListAdapter; +import com.newsblur.databinding.FragmentFolderfeedlistBinding; import com.newsblur.domain.Feed; +import com.newsblur.domain.Folder; +import com.newsblur.domain.SavedSearch; import com.newsblur.domain.SocialFeed; import com.newsblur.util.AppConstants; import com.newsblur.util.FeedSet; @@ -64,11 +64,12 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen private static final int FOLDERS_LOADER = 2; private static final int FEEDS_LOADER = 3; private static final int SAVEDCOUNT_LOADER = 4; + private static final int SAVED_SEARCH_LOADER = 5; private FolderListAdapter adapter; public StateFilter currentState = StateFilter.SOME; private SharedPreferences sharedPreferences; - @Bind(R.id.folderfeed_list) ExpandableListView list; + private FragmentFolderfeedlistBinding binding; public boolean firstCursorSeenYet = false; // the two-step context menu for feeds requires us to temp store the feed long-pressed so @@ -112,6 +113,8 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen return FeedUtils.dbHelper.getFeedsLoader(); case SAVEDCOUNT_LOADER: return FeedUtils.dbHelper.getSavedStoryCountsLoader(); + case SAVED_SEARCH_LOADER: + return FeedUtils.dbHelper.getSavedSearchLoader(); default: throw new IllegalArgumentException("unknown loader created"); } @@ -140,6 +143,9 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen case SAVEDCOUNT_LOADER: adapter.setStarredCountCursor(cursor); break; + case SAVED_SEARCH_LOADER: + adapter.setSavedSearchesCursor(cursor); + break; default: throw new IllegalArgumentException("unknown loader created"); } @@ -163,6 +169,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen getLoaderManager().restartLoader(FOLDERS_LOADER, null, this); getLoaderManager().restartLoader(FEEDS_LOADER, null, this); getLoaderManager().restartLoader(SAVEDCOUNT_LOADER, null, this); + getLoaderManager().restartLoader(SAVED_SEARCH_LOADER, null, this); } catch (Exception e) { // on heavily loaded devices, the time between isAdded() going false // and the loader subsystem shutting down can be nontrivial, causing @@ -179,6 +186,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen getLoaderManager().initLoader(FOLDERS_LOADER, null, this); getLoaderManager().initLoader(FEEDS_LOADER, null, this); getLoaderManager().initLoader(SAVEDCOUNT_LOADER, null, this); + getLoaderManager().initLoader(SAVED_SEARCH_LOADER, null, this); } } } @@ -190,20 +198,20 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_folderfeedlist, container); - ButterKnife.bind(this, v); + binding = FragmentFolderfeedlistBinding.bind(v); - list.setGroupIndicator(UIUtils.getDrawable(getActivity(), R.drawable.transparent)); - list.setOnCreateContextMenuListener(this); - list.setOnChildClickListener(this); - list.setOnGroupClickListener(this); - list.setOnGroupCollapseListener(this); - list.setOnGroupExpandListener(this); + binding.folderfeedList.setGroupIndicator(UIUtils.getDrawable(getActivity(), R.drawable.transparent)); + binding.folderfeedList.setOnCreateContextMenuListener(this); + binding.folderfeedList.setOnChildClickListener(this); + binding.folderfeedList.setOnGroupClickListener(this); + binding.folderfeedList.setOnGroupCollapseListener(this); + binding.folderfeedList.setOnGroupExpandListener(this); - adapter.listBackref = new WeakReference(list); // see note in adapter about backref - list.setAdapter(adapter); + adapter.listBackref = new WeakReference(binding.folderfeedList); // see note in adapter about backref + binding.folderfeedList.setAdapter(adapter); // Main activity needs to listen for scrolls to prevent refresh from firing unnecessarily - list.setOnScrollListener((android.widget.AbsListView.OnScrollListener) getActivity()); + binding.folderfeedList.setOnScrollListener((android.widget.AbsListView.OnScrollListener) getActivity()); return v; } @@ -215,18 +223,18 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen */ public void checkOpenFolderPreferences() { // make sure we didn't beat construction - if ((this.list == null) || (this.sharedPreferences == null)) return; + if ((this.binding.folderfeedList == null) || (this.sharedPreferences == null)) return; for (int i = 0; i < adapter.getGroupCount(); i++) { String flatGroupName = adapter.getGroupUniqueName(i); if (sharedPreferences.getBoolean(AppConstants.FOLDER_PRE + "_" + flatGroupName, true)) { - if (list.isGroupExpanded(i) == false) { - list.expandGroup(i); + if (binding.folderfeedList.isGroupExpanded(i) == false) { + binding.folderfeedList.expandGroup(i); adapter.setFolderClosed(flatGroupName, false); } } else { - if (list.isGroupExpanded(i) == true) { - list.collapseGroup(i); + if (binding.folderfeedList.isGroupExpanded(i) == true) { + binding.folderfeedList.collapseGroup(i); adapter.setFolderClosed(flatGroupName, true); } } @@ -249,11 +257,14 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen if (adapter.isRowGlobalSharedStories(groupPosition)) break; if (adapter.isRowAllSharedStories(groupPosition)) break; if (adapter.isRowInfrequentStories(groupPosition)) break; + if (adapter.isRowSavedSearches(groupPosition)) break; inflater.inflate(R.menu.context_folder, menu); if (adapter.isRowAllStories(groupPosition)) { menu.removeItem(R.id.menu_mute_folder); menu.removeItem(R.id.menu_unmute_folder); + menu.removeItem(R.id.menu_delete_folder); + menu.removeItem(R.id.menu_rename_folder); } break; @@ -272,9 +283,22 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen menu.removeItem(R.id.menu_instafetch_feed); menu.removeItem(R.id.menu_intel); menu.removeItem(R.id.menu_rename_feed); + menu.removeItem(R.id.menu_delete_saved_search); + } else if (adapter.isRowSavedSearches(groupPosition)) { + menu.removeItem(R.id.menu_mark_feed_as_read); + menu.removeItem(R.id.menu_delete_feed); + menu.removeItem(R.id.menu_unfollow); + menu.removeItem(R.id.menu_choose_folders); + menu.removeItem(R.id.menu_rename_feed); + menu.removeItem(R.id.menu_notifications); + menu.removeItem(R.id.menu_mute_feed); + menu.removeItem(R.id.menu_unmute_feed); + menu.removeItem(R.id.menu_instafetch_feed); + menu.removeItem(R.id.menu_intel); } else { // normal feeds menu.removeItem(R.id.menu_unfollow); + menu.removeItem(R.id.menu_delete_saved_search); Feed feed = adapter.getFeed(groupPosition, childPosition); if (feed.active) { @@ -359,7 +383,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen } else if (item.getItemId() == R.id.menu_rename_feed) { Feed feed = adapter.getFeed(groupPosition, childPosition); if (feed != null) { - DialogFragment renameFeedFragment = RenameFeedFragment.newInstance(feed); + DialogFragment renameFeedFragment = RenameDialogFragment.newInstance(feed); renameFeedFragment.show(getFragmentManager(), "dialog"); } } else if (item.getItemId() == R.id.menu_mute_feed) { @@ -379,6 +403,22 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen } else if (item.getItemId() == R.id.menu_intel) { FeedIntelTrainerFragment intelFrag = FeedIntelTrainerFragment.newInstance(adapter.getFeed(groupPosition, childPosition), adapter.getChild(groupPosition, childPosition)); intelFrag.show(getFragmentManager(), FeedIntelTrainerFragment.class.getName()); + } else if (item.getItemId() == R.id.menu_delete_saved_search) { + SavedSearch savedSearch = adapter.getSavedSearch(childPosition); + if (savedSearch != null) { + DialogFragment deleteFeedFragment = DeleteFeedFragment.newInstance(savedSearch); + deleteFeedFragment.show(getFragmentManager(), "dialog"); + } + } else if (item.getItemId() == R.id.menu_delete_folder) { + Folder folder = adapter.getGroupFolder(groupPosition); + String folderParentName = folder.getFirstParentName(); + DeleteFolderFragment deleteFolderFragment = DeleteFolderFragment.newInstance(folder.name, folderParentName); + deleteFolderFragment.show(getFragmentManager(), deleteFolderFragment.getTag()); + } else if (item.getItemId() == R.id.menu_rename_folder) { + Folder folder = adapter.getGroupFolder(groupPosition); + String folderParentName = folder.getFirstParentName(); + RenameDialogFragment renameDialogFragment = RenameDialogFragment.newInstance(folder.name, folderParentName); + renameDialogFragment.show(getFragmentManager(), renameDialogFragment.getTag()); } return super.onContextItemSelected(item); @@ -449,6 +489,9 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen i = new Intent(getActivity(), ReadStoriesItemsList.class); } else if (adapter.isRowSavedStories(groupPosition)) { i = new Intent(getActivity(), SavedStoriesItemsList.class); + } else if (adapter.isRowSavedSearches(groupPosition)) { + // group not clickable + return true; } else { i = new Intent(getActivity(), FolderItemsList.class); String canonicalFolderName = adapter.getGroupFolderName(groupPosition); @@ -472,6 +515,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen // these shouldn't ever be collapsible if (adapter.isRowRootFolder(groupPosition)) return; if (adapter.isRowReadStories(groupPosition)) return; + if (adapter.isRowSavedSearches(groupPosition)) return; String flatGroupName = adapter.getGroupUniqueName(groupPosition); // save the expanded preference, since the widget likes to forget it @@ -490,6 +534,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen // these shouldn't ever be collapsible if (adapter.isRowRootFolder(groupPosition)) return; if (adapter.isRowReadStories(groupPosition)) return; + if (adapter.isRowSavedSearches(groupPosition)) return; String flatGroupName = adapter.getGroupUniqueName(groupPosition); // save the collapsed preference, since the widget likes to forget it @@ -515,7 +560,9 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen Intent intent = new Intent(getActivity(), SavedStoriesItemsList.class); intent.putExtra(ItemsList.EXTRA_FEED_SET, fs); getActivity().startActivity(intent); - } else { + } else if (adapter.isRowSavedSearches(groupPosition)) { + openSavedSearch(adapter.getSavedSearch(childPosition)); + } else { Feed feed = adapter.getFeed(groupPosition, childPosition); // NB: FeedItemsList needs the name of the containing folder, but this is not the same as setting // a folder name on the FeedSet and making it into a folder-type set. it is just a single feed, @@ -534,6 +581,53 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen return true; } + private void openSavedSearch(SavedSearch savedSearch) { + Intent intent = null; + FeedSet fs = null; + String feedId = savedSearch.feedId; + if (feedId.equals("river:")) { + // all site stories + intent = new Intent(getActivity(), AllStoriesItemsList.class); + fs = FeedSet.allFeeds(); + } else if (feedId.equals("river:infrequent")) { + // infrequent stories + intent = new Intent(getActivity(), InfrequentItemsList.class); + fs = FeedSet.infrequentFeeds(); + } else if (feedId.startsWith("river:")) { + intent = new Intent(getActivity(), FolderItemsList.class); + String folderName = feedId.replace("river:", ""); + fs = FeedUtils.feedSetFromFolderName(folderName); + intent.putExtra(FolderItemsList.EXTRA_FOLDER_NAME, folderName); + } else if (feedId.equals("read")) { + intent = new Intent(getActivity(), ReadStoriesItemsList.class); + fs = FeedSet.allRead(); + } else if (feedId.equals("starred")) { + intent = new Intent(getActivity(), SavedStoriesItemsList.class); + fs = FeedSet.allSaved(); + } else if (feedId.startsWith("starred:")) { + intent = new Intent(getActivity(), SavedStoriesItemsList.class); + fs = FeedSet.singleSavedTag(feedId.replace("starred:", "")); + } else if (feedId.startsWith("feed:")) { + intent = new Intent(getActivity(), FeedItemsList.class); + String cleanFeedId = feedId.replace("feed:", ""); + Feed feed = FeedUtils.getFeed(cleanFeedId); + fs = FeedSet.singleFeed(cleanFeedId); + intent.putExtra(FeedItemsList.EXTRA_FEED, feed); + } else if (feedId.startsWith("social:")) { + intent = new Intent(getActivity(), SocialFeedItemsList.class); + String cleanFeedId = feedId.replace("social:", ""); + fs = FeedSet.singleFeed(cleanFeedId); + Feed feed = FeedUtils.getFeed(cleanFeedId); + intent.putExtra(FeedItemsList.EXTRA_FEED, feed); + } + + if (intent != null) { + fs.setSearchQuery(savedSearch.query); + intent.putExtra(ItemsList.EXTRA_FEED_SET, fs); + startActivity(intent); + } + } + public void setTextSize(Float size) { if (adapter != null) { adapter.setTextSize(size); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/InfrequentCutoffDialogFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/InfrequentCutoffDialogFragment.java index c5216d8de..4d3bfd250 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/InfrequentCutoffDialogFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/InfrequentCutoffDialogFragment.java @@ -1,28 +1,22 @@ package com.newsblur.fragment; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.RadioButton; - -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; import com.newsblur.R; +import com.newsblur.databinding.InfrequentCutoffDialogBinding; public class InfrequentCutoffDialogFragment extends DialogFragment { private static String CURRENT_CUTOFF = "currentCutoff"; private int currentValue; - @Bind(R.id.radio_5) RadioButton button5; - @Bind(R.id.radio_15) RadioButton button15; - @Bind(R.id.radio_30) RadioButton button30; - @Bind(R.id.radio_60) RadioButton button60; - @Bind(R.id.radio_90) RadioButton button90; + private InfrequentCutoffDialogBinding binding; public static InfrequentCutoffDialogFragment newInstance(int currentValue) { InfrequentCutoffDialogFragment dialog = new InfrequentCutoffDialogFragment(); @@ -42,13 +36,13 @@ public class InfrequentCutoffDialogFragment extends DialogFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { currentValue = getArguments().getInt(CURRENT_CUTOFF); View v = inflater.inflate(R.layout.infrequent_cutoff_dialog, null); - ButterKnife.bind(this, v); + binding = InfrequentCutoffDialogBinding.bind(v); - button5.setChecked(currentValue == 5); - button15.setChecked(currentValue == 15); - button30.setChecked(currentValue == 30); - button60.setChecked(currentValue == 60); - button90.setChecked(currentValue == 90); + binding.radio5.setChecked(currentValue == 5); + binding.radio15.setChecked(currentValue == 15); + binding.radio30.setChecked(currentValue == 30); + binding.radio60.setChecked(currentValue == 60); + binding.radio90.setChecked(currentValue == 90); getDialog().setTitle(R.string.infrequent_choice_title); getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM; @@ -56,31 +50,66 @@ public class InfrequentCutoffDialogFragment extends DialogFragment { return v; } - @OnClick(R.id.radio_5) void select5() { + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.radio5.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + select5(); + } + }); + binding.radio15.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + select15(); + } + }); + binding.radio30.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + select30(); + } + }); + binding.radio60.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + select60(); + } + }); + binding.radio90.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + select90(); + } + }); + } + + private void select5() { if (currentValue != 5) { ((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(5); } dismiss(); } - @OnClick(R.id.radio_15) void select15() { + private void select15() { if (currentValue != 15) { ((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(15); } dismiss(); } - @OnClick(R.id.radio_30) void select30() { + private void select30() { if (currentValue != 30) { ((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(30); } dismiss(); } - @OnClick(R.id.radio_60) void select60() { + private void select60() { if (currentValue != 60) { ((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(60); } dismiss(); } - @OnClick(R.id.radio_90) void select90() { + private void select90() { if (currentValue != 90) { ((InfrequentCutoffChangedListener) getActivity()).infrequentCutoffChanged(90); } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ItemSetFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ItemSetFragment.java index 5d2bb3de1..1bbf5571d 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ItemSetFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ItemSetFragment.java @@ -15,17 +15,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.TextView; - -import butterknife.ButterKnife; -import butterknife.Bind; import com.newsblur.R; import com.newsblur.activity.ItemsList; import com.newsblur.activity.NbActivity; import com.newsblur.database.StoryViewAdapter; +import com.newsblur.databinding.FragmentItemgridBinding; import com.newsblur.domain.Story; import com.newsblur.service.NBSyncService; import com.newsblur.util.FeedSet; @@ -50,21 +46,17 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC private final static int GRID_SPACING_DP = 5; private int gridSpacingPx; - @Bind(R.id.itemgridfragment_grid) RecyclerView itemGrid; private GridLayoutManager layoutManager; private StoryViewAdapter adapter; // an optional pending scroll state to restore. private Parcelable gridState; // loading indicator for when stories are absent or stale (at top of list) - @Bind(R.id.top_loading_throb) ProgressThrobber topProgressView; + // R.id.top_loading_throb + // loading indicator for when stories are present and fresh (at bottom of list) protected ProgressThrobber bottomProgressView; - @Bind(R.id.empty_view) View emptyView; - @Bind(R.id.empty_view_text) TextView emptyViewText; - @Bind(R.id.empty_view_image) ImageView emptyViewImage; - private View fleuronFooter; // the fleuron has padding that can't be calculated until after layout, but only changes // rarely thereafter @@ -76,6 +68,8 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC public int indexOfLastUnread = -1; public boolean fullFlingComplete = false; + private FragmentItemgridBinding binding; + public static ItemSetFragment newInstance() { ItemSetFragment fragment = new ItemSetFragment(); Bundle arguments = new Bundle(); @@ -123,13 +117,13 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_itemgrid, null); - ButterKnife.bind(this, v); + binding = FragmentItemgridBinding.bind(v); // disable the throbbers if animations are going to have a zero time scale boolean isDisableAnimations = ViewUtils.isPowerSaveMode(getActivity()); - topProgressView.setEnabled(!isDisableAnimations); - topProgressView.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1), + binding.topLoadingThrob.setEnabled(!isDisableAnimations); + binding.topLoadingThrob.setColors(UIUtils.getColor(getActivity(), R.color.refresh_1), UIUtils.getColor(getActivity(), R.color.refresh_2), UIUtils.getColor(getActivity(), R.color.refresh_3), UIUtils.getColor(getActivity(), R.color.refresh_4)); @@ -145,11 +139,11 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC fleuronFooter = inflater.inflate(R.layout.row_fleuron, null); fleuronFooter.setVisibility(View.INVISIBLE); - itemGrid.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { + binding.itemgridfragmentGrid.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - itemGridWidthPx = itemGrid.getMeasuredWidth(); - itemGrid.getViewTreeObserver().removeOnGlobalLayoutListener(this); + itemGridWidthPx = binding.itemgridfragmentGrid.getMeasuredWidth(); + binding.itemgridfragmentGrid.getViewTreeObserver().removeOnGlobalLayoutListener(this); updateStyle(); } }); @@ -158,11 +152,11 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC calcColumnCount(listStyle); layoutManager = new GridLayoutManager(getActivity(), columnCount); - itemGrid.setLayoutManager(layoutManager); + binding.itemgridfragmentGrid.setLayoutManager(layoutManager); setupAnimSpeeds(); calcGridSpacing(listStyle); - itemGrid.addItemDecoration(new RecyclerView.ItemDecoration() { + binding.itemgridfragmentGrid.addItemDecoration(new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.set(gridSpacingPx, gridSpacingPx, gridSpacingPx, gridSpacingPx); @@ -172,7 +166,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC adapter = new StoryViewAdapter(((NbActivity) getActivity()), this, getFeedSet(), listStyle); adapter.addFooterView(footerView); adapter.addFooterView(fleuronFooter); - itemGrid.setAdapter(adapter); + binding.itemgridfragmentGrid.setAdapter(adapter); // the layout manager needs to know that the footer rows span all the way across layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @@ -187,14 +181,14 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC } }); - itemGrid.addOnScrollListener(new RecyclerView.OnScrollListener() { + binding.itemgridfragmentGrid.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { ItemSetFragment.this.onScrolled(recyclerView, dx, dy); } }); - setupGestureDetector(itemGrid); + setupGestureDetector(binding.itemgridfragmentGrid); return v; } @@ -281,13 +275,13 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC } protected void updateAdapter(Cursor cursor) { - adapter.swapCursor(cursor, itemGrid, gridState); + adapter.swapCursor(cursor, binding.itemgridfragmentGrid, gridState); gridState = null; adapter.updateFeedSet(getFeedSet()); if ((cursor != null) && (cursor.getCount() > 0)) { - emptyView.setVisibility(View.INVISIBLE); + binding.emptyView.setVisibility(View.INVISIBLE); } else { - emptyView.setVisibility(View.VISIBLE); + binding.emptyView.setVisibility(View.VISIBLE); } // though we have stories, we might not yet have as many as we want @@ -305,38 +299,38 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC calcFleuronPadding(); if (getFeedSet().isMuted()) { - emptyViewText.setText(R.string.empty_list_view_muted_feed); - emptyViewText.setTypeface(null, Typeface.NORMAL); - emptyViewImage.setVisibility(View.VISIBLE); - topProgressView.setVisibility(View.INVISIBLE); + binding.emptyViewText.setText(R.string.empty_list_view_muted_feed); + binding.emptyViewText.setTypeface(null, Typeface.NORMAL); + binding.emptyViewImage.setVisibility(View.VISIBLE); + binding.topLoadingThrob.setVisibility(View.INVISIBLE); bottomProgressView.setVisibility(View.INVISIBLE); return; } if ( (!cursorSeenYet) || NBSyncService.isFeedSetSyncing(getFeedSet(), getActivity()) ) { - emptyViewText.setText(R.string.empty_list_view_loading); - emptyViewText.setTypeface(null, Typeface.ITALIC); - emptyViewImage.setVisibility(View.INVISIBLE); + binding.emptyViewText.setText(R.string.empty_list_view_loading); + binding.emptyViewText.setTypeface(null, Typeface.ITALIC); + binding.emptyViewImage.setVisibility(View.INVISIBLE); if (NBSyncService.isFeedSetStoriesFresh(getFeedSet())) { - topProgressView.setVisibility(View.INVISIBLE); + binding.topLoadingThrob.setVisibility(View.INVISIBLE); bottomProgressView.setVisibility(View.VISIBLE); } else { - topProgressView.setVisibility(View.VISIBLE); + binding.topLoadingThrob.setVisibility(View.VISIBLE); bottomProgressView.setVisibility(View.GONE); } fleuronFooter.setVisibility(View.INVISIBLE); } else { ReadFilter readFilter = PrefsUtils.getReadFilter(getActivity(), getFeedSet()); if (readFilter == ReadFilter.UNREAD) { - emptyViewText.setText(R.string.empty_list_view_no_stories_unread); + binding.emptyViewText.setText(R.string.empty_list_view_no_stories_unread); } else { - emptyViewText.setText(R.string.empty_list_view_no_stories); + binding.emptyViewText.setText(R.string.empty_list_view_no_stories); } - emptyViewText.setTypeface(null, Typeface.NORMAL); - emptyViewImage.setVisibility(View.VISIBLE); + binding.emptyViewText.setTypeface(null, Typeface.NORMAL); + binding.emptyViewImage.setVisibility(View.VISIBLE); - topProgressView.setVisibility(View.INVISIBLE); + binding.topLoadingThrob.setVisibility(View.INVISIBLE); bottomProgressView.setVisibility(View.INVISIBLE); if (cursorSeenYet && NBSyncService.isFeedSetExhausted(getFeedSet()) && (adapter.getRawStoryCount() > 0)) { fleuronFooter.setVisibility(View.VISIBLE); @@ -411,7 +405,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC targetMovDuration = 0L; } - RecyclerView.ItemAnimator anim = itemGrid.getItemAnimator(); + RecyclerView.ItemAnimator anim = binding.itemgridfragmentGrid.getItemAnimator(); anim.setAddDuration((long) ((anim.getAddDuration() + targetAddDuration)/2L)); anim.setMoveDuration((long) ((anim.getMoveDuration() + targetMovDuration)/2L)); } @@ -429,7 +423,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC // past the last item, which can be confusing to users who don't know about or need the offset if ( (!fullFlingComplete) && (layoutManager.findLastCompletelyVisibleItemPosition() >= adapter.getStoryCount()) ) { - itemGrid.stopScroll(); + binding.itemgridfragmentGrid.stopScroll(); // but after halting at the end once, do allow scrolling past the bottom fullFlingComplete = true; } @@ -439,7 +433,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC (layoutManager.findLastCompletelyVisibleItemPosition() >= indexOfLastUnread) ) { // but don't interrupt if already past the last unread if (indexOfLastUnread >= layoutManager.findFirstCompletelyVisibleItemPosition()) { - itemGrid.stopScroll(); + binding.itemgridfragmentGrid.stopScroll(); } indexOfLastUnread = -1; } @@ -507,7 +501,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC */ private void calcFleuronPadding() { if (fleuronResized) return; - int listHeight = itemGrid.getMeasuredHeight(); + int listHeight = binding.itemgridfragmentGrid.getMeasuredHeight(); View innerView = fleuronFooter.findViewById(R.id.fleuron); ViewGroup.LayoutParams oldLayout = innerView.getLayoutParams(); ViewGroup.MarginLayoutParams newLayout = new LinearLayout.LayoutParams(oldLayout); @@ -526,7 +520,7 @@ public class ItemSetFragment extends NbFragment implements LoaderManager.LoaderC @Override public void onSaveInstanceState (Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(BUNDLE_GRIDSTATE, itemGrid.getLayoutManager().onSaveInstanceState()); + outState.putParcelable(BUNDLE_GRIDSTATE, binding.itemgridfragmentGrid.getLayoutManager().onSaveInstanceState()); } } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/LoginProgressFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/LoginProgressFragment.java index fa5c0c75f..998800ece 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/LoginProgressFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/LoginProgressFragment.java @@ -11,17 +11,12 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; -import butterknife.ButterKnife; -import butterknife.Bind; - import com.newsblur.R; import com.newsblur.activity.Login; import com.newsblur.activity.Main; +import com.newsblur.databinding.FragmentLoginprogressBinding; import com.newsblur.network.APIManager; import com.newsblur.network.domain.LoginResponse; import com.newsblur.util.PrefsUtils; @@ -30,14 +25,10 @@ import com.newsblur.util.UIUtils; public class LoginProgressFragment extends Fragment { private APIManager apiManager; - @Bind(R.id.login_logging_in) TextView updateStatus; - @Bind(R.id.login_retrieving_feeds) TextView retrievingFeeds; - @Bind(R.id.login_profile_picture) ImageView loginProfilePicture; - @Bind(R.id.login_feed_progress) ProgressBar feedProgress; - @Bind(R.id.login_logging_in_progress) ProgressBar loggingInProgress; private LoginTask loginTask; private String username; private String password; + private FragmentLoginprogressBinding binding; public static LoginProgressFragment getInstance(String username, String password) { LoginProgressFragment fragment = new LoginProgressFragment(); @@ -61,7 +52,7 @@ public class LoginProgressFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_loginprogress, null); - ButterKnife.bind(this, v); + binding = FragmentLoginprogressBinding.bind(v); loginTask = new LoginTask(); loginTask.execute(); @@ -73,7 +64,7 @@ public class LoginProgressFragment extends Fragment { @Override protected void onPreExecute() { Animation a = AnimationUtils.loadAnimation(getActivity(), R.anim.text_up); - updateStatus.startAnimation(a); + binding.loginLoggingIn.startAnimation(a); } @Override @@ -90,20 +81,20 @@ public class LoginProgressFragment extends Fragment { if (c == null) return; // we might have run past the lifecycle of the activity if (!result.isError()) { final Animation a = AnimationUtils.loadAnimation(c, R.anim.text_down); - updateStatus.setText(R.string.login_logged_in); - loggingInProgress.setVisibility(View.GONE); - updateStatus.startAnimation(a); + binding.loginLoggingIn.setText(R.string.login_logged_in); + binding.loginLoggingInProgress.setVisibility(View.GONE); + binding.loginLoggingIn.startAnimation(a); Bitmap userImage = PrefsUtils.getUserImage(c); if (userImage != null ) { - loginProfilePicture.setVisibility(View.VISIBLE); - loginProfilePicture.setImageBitmap(UIUtils.clipAndRound(userImage, 10f, false)); + binding.loginProfilePicture.setVisibility(View.VISIBLE); + binding.loginProfilePicture.setImageBitmap(UIUtils.clipAndRound(userImage, 10f, false)); } - feedProgress.setVisibility(View.VISIBLE); + binding.loginFeedProgress.setVisibility(View.VISIBLE); final Animation b = AnimationUtils.loadAnimation(c, R.anim.text_up); - retrievingFeeds.setText(R.string.login_retrieving_feeds); - retrievingFeeds.startAnimation(b); + binding.loginRetrievingFeeds.setText(R.string.login_retrieving_feeds); + binding.loginFeedProgress.startAnimation(b); Intent startMain = new Intent(getActivity(), Main.class); c.startActivity(startMain); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java index 00e98624c..6c2e7ed9a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java @@ -3,6 +3,8 @@ package com.newsblur.fragment; import android.content.Intent; import android.os.Bundle; import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.view.KeyEvent; @@ -10,39 +12,27 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import android.widget.ViewSwitcher; - -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; import com.newsblur.R; import com.newsblur.activity.LoginProgress; import com.newsblur.activity.RegisterProgress; +import com.newsblur.databinding.FragmentLoginregisterBinding; import com.newsblur.network.APIConstants; import com.newsblur.util.AppConstants; import com.newsblur.util.PrefsUtils; public class LoginRegisterFragment extends Fragment { - @Bind(R.id.login_username) EditText username; - @Bind(R.id.login_password) EditText password; - @Bind(R.id.registration_username) EditText register_username; - @Bind(R.id.registration_password) EditText register_password; - @Bind(R.id.registration_email) EditText register_email; - @Bind(R.id.login_viewswitcher) ViewSwitcher viewSwitcher; - @Bind(R.id.login_custom_server) View customServer; - @Bind(R.id.login_custom_server_value) EditText customServerValue; + private FragmentLoginregisterBinding binding; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View v = inflater.inflate(R.layout.fragment_loginregister, container, false); - ButterKnife.bind(this, v); + binding = FragmentLoginregisterBinding.bind(v); - password.setOnEditorActionListener(new OnEditorActionListener() { + binding.loginPassword.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView arg0, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE ) { @@ -52,7 +42,7 @@ public class LoginRegisterFragment extends Fragment { } }); - register_email.setOnEditorActionListener(new OnEditorActionListener() { + binding.registrationEmail.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView arg0, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE ) { @@ -65,36 +55,77 @@ public class LoginRegisterFragment extends Fragment { return v; } - @OnClick(R.id.login_button) void logIn() { - if (!TextUtils.isEmpty(username.getText().toString())) { + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.loginButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + logIn(); + } + }); + binding.registrationButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + signUp(); + } + }); + binding.loginChangeToLogin.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showLogin(); + } + }); + binding.loginChangeToRegister.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showRegister(); + } + }); + binding.loginForgotPassword.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + launchForgotPasswordPage(); + } + }); + binding.loginCustomServer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showCustomServer(); + } + }); + } + + private void logIn() { + if (!TextUtils.isEmpty(binding.loginUsername.getText().toString())) { // set the custom server endpoint before any API access, even the cookie fetch. - APIConstants.setCustomServer(customServerValue.getText().toString()); - PrefsUtils.saveCustomServer(getActivity(), customServerValue.getText().toString()); + APIConstants.setCustomServer(binding.loginCustomServerValue.getText().toString()); + PrefsUtils.saveCustomServer(getActivity(), binding.loginCustomServerValue.getText().toString()); Intent i = new Intent(getActivity(), LoginProgress.class); - i.putExtra("username", username.getText().toString()); - i.putExtra("password", password.getText().toString()); + i.putExtra("username", binding.loginUsername.getText().toString()); + i.putExtra("password", binding.loginUsername.getText().toString()); startActivity(i); } } - @OnClick(R.id.registration_button) void signUp() { + private void signUp() { Intent i = new Intent(getActivity(), RegisterProgress.class); - i.putExtra("username", register_username.getText().toString()); - i.putExtra("password", register_password.getText().toString()); - i.putExtra("email", register_email.getText().toString()); + i.putExtra("username", binding.registrationUsername.getText().toString()); + i.putExtra("password", binding.registrationPassword.getText().toString()); + i.putExtra("email", binding.registrationEmail.getText().toString()); startActivity(i); } - @OnClick(R.id.login_change_to_login) void showLogin() { - viewSwitcher.showPrevious(); + private void showLogin() { + binding.loginViewswitcher.showPrevious(); } - @OnClick(R.id.login_change_to_register) void showRegister() { - viewSwitcher.showNext(); + private void showRegister() { + binding.loginViewswitcher.showNext(); } - @OnClick(R.id.login_forgot_password) void launchForgotPasswordPage() { + private void launchForgotPasswordPage() { try { Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(AppConstants.FORGOT_PASWORD_URL)); @@ -104,9 +135,8 @@ public class LoginRegisterFragment extends Fragment { } } - @OnClick(R.id.login_custom_server) void showCustomServer() { - customServer.setVisibility(View.GONE); - customServerValue.setVisibility(View.VISIBLE); + private void showCustomServer() { + binding.loginCustomServer.setVisibility(View.GONE); + binding.loginCustomServerValue.setVisibility(View.VISIBLE); } - -} +} \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadFilterDialogFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadFilterDialogFragment.java index 697dcd203..e65c8a9f0 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadFilterDialogFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadFilterDialogFragment.java @@ -1,19 +1,17 @@ package com.newsblur.fragment; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; -import android.widget.RadioButton; - -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; import com.newsblur.R; +import com.newsblur.databinding.ReadfilterDialogBinding; import com.newsblur.util.ReadFilter; import com.newsblur.util.ReadFilterChangedListener; @@ -21,8 +19,7 @@ public class ReadFilterDialogFragment extends DialogFragment { private static String CURRENT_FILTER = "currentFilter"; private ReadFilter currentValue; - @Bind(R.id.radio_all) RadioButton allButton; - @Bind(R.id.radio_unread) RadioButton unreadButton; + private ReadfilterDialogBinding binding; public static ReadFilterDialogFragment newInstance(ReadFilter currentValue) { ReadFilterDialogFragment dialog = new ReadFilterDialogFragment(); @@ -42,10 +39,10 @@ public class ReadFilterDialogFragment extends DialogFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { currentValue = (ReadFilter) getArguments().getSerializable(CURRENT_FILTER); View v = inflater.inflate(R.layout.readfilter_dialog, null); - ButterKnife.bind(this, v); + binding = ReadfilterDialogBinding.bind(v); - allButton.setChecked(currentValue == ReadFilter.ALL); - unreadButton.setChecked(currentValue == ReadFilter.UNREAD); + binding.radioAll.setChecked(currentValue == ReadFilter.ALL); + binding.radioUnread.setChecked(currentValue == ReadFilter.UNREAD); getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM; @@ -53,14 +50,31 @@ public class ReadFilterDialogFragment extends DialogFragment { return v; } - @OnClick(R.id.radio_all) void selectAll() { + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.radioAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selectAll(); + } + }); + binding.radioUnread.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selectUnread(); + } + }); + } + + private void selectAll() { if (currentValue != ReadFilter.ALL) { ((ReadFilterChangedListener) getActivity()).readFilterChanged(ReadFilter.ALL); } dismiss(); } - @OnClick(R.id.radio_unread) void selectUnread() { + private void selectUnread() { if (currentValue != ReadFilter.UNREAD) { ((ReadFilterChangedListener) getActivity()).readFilterChanged(ReadFilter.UNREAD); } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingFontDialogFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingFontDialogFragment.java index 1b7b36b2c..443dadee1 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingFontDialogFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingFontDialogFragment.java @@ -1,21 +1,19 @@ package com.newsblur.fragment; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; -import android.widget.RadioButton; import com.newsblur.R; +import com.newsblur.databinding.ReadingfontDialogBinding; import com.newsblur.util.ReadingFontChangedListener; -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; - /** * Created by mark on 02/05/2017. */ @@ -26,14 +24,7 @@ public class ReadingFontDialogFragment extends DialogFragment { private String currentValue; - @Bind(R.id.radio_anonymous) RadioButton anonymousButton; - @Bind(R.id.radio_chronicle) RadioButton chronicleButton; - @Bind(R.id.radio_default) RadioButton defaultButton; - @Bind(R.id.radio_gotham) RadioButton gothamButton; - @Bind(R.id.radio_noto_sans) RadioButton notoSansButton; - @Bind(R.id.radio_noto_serif) RadioButton notoSerifButton; - @Bind(R.id.radio_open_sans_condensed) RadioButton openSansCondensedButton; - @Bind(R.id.radio_whitney) RadioButton whitneyButton; + private ReadingfontDialogBinding binding; public static ReadingFontDialogFragment newInstance(String selectedFont) { ReadingFontDialogFragment dialog = new ReadingFontDialogFragment(); @@ -52,16 +43,16 @@ public class ReadingFontDialogFragment extends DialogFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { currentValue = getArguments().getString(SELECTED_FONT); View v = inflater.inflate(R.layout.readingfont_dialog, null); - ButterKnife.bind(this, v); + binding = ReadingfontDialogBinding.bind(v); - anonymousButton.setChecked(currentValue.equals(getString(R.string.anonymous_pro_font_prefvalue))); - chronicleButton.setChecked(currentValue.equals(getString(R.string.chronicle_font_prefvalue))); - defaultButton.setChecked(currentValue.equals(getString(R.string.default_font_prefvalue))); - gothamButton.setChecked(currentValue.equals(getString(R.string.gotham_narrow_font_prefvalue))); - notoSansButton.setChecked(currentValue.equals(getString(R.string.noto_sans_font_prefvalue))); - notoSerifButton.setChecked(currentValue.equals(getString(R.string.noto_serif_font_prefvalue))); - openSansCondensedButton.setChecked(currentValue.equals(getString(R.string.open_sans_condensed_font_prefvalue))); - whitneyButton.setChecked(currentValue.equals(getString(R.string.whitney_font_prefvalue))); + binding.radioAnonymous.setChecked(currentValue.equals(getString(R.string.anonymous_pro_font_prefvalue))); + binding.radioChronicle.setChecked(currentValue.equals(getString(R.string.chronicle_font_prefvalue))); + binding.radioDefault.setChecked(currentValue.equals(getString(R.string.default_font_prefvalue))); + binding.radioGotham.setChecked(currentValue.equals(getString(R.string.gotham_narrow_font_prefvalue))); + binding.radioNotoSans.setChecked(currentValue.equals(getString(R.string.noto_sans_font_prefvalue))); + binding.radioNotoSerif.setChecked(currentValue.equals(getString(R.string.noto_serif_font_prefvalue))); + binding.radioOpenSansCondensed.setChecked(currentValue.equals(getString(R.string.open_sans_condensed_font_prefvalue))); + binding.radioWhitney.setChecked(currentValue.equals(getString(R.string.whitney_font_prefvalue))); getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM; @@ -69,8 +60,57 @@ public class ReadingFontDialogFragment extends DialogFragment { return v; } - @OnClick(R.id.radio_anonymous) void selectAnonymousPro() { - switchFont(getString(R.string.anonymous_pro_font_prefvalue)); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.radioAnonymous.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.anonymous_pro_font_prefvalue)); + } + }); + binding.radioDefault.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.default_font_prefvalue)); + } + }); + binding.radioChronicle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.chronicle_font_prefvalue)); + } + }); + binding.radioGotham.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.gotham_narrow_font_prefvalue)); + } + }); + binding.radioNotoSans.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.noto_sans_font_prefvalue)); + } + }); + binding.radioNotoSerif.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.noto_serif_font_prefvalue)); + } + }); + binding.radioOpenSansCondensed.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.open_sans_condensed_font_prefvalue)); + } + }); + binding.radioWhitney.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchFont(getString(R.string.whitney_font_prefvalue)); + } + }); } private void switchFont(String newValue) { @@ -79,32 +119,4 @@ public class ReadingFontDialogFragment extends DialogFragment { currentValue = newValue; } } - - @OnClick(R.id.radio_chronicle) void selectChronicle() { - switchFont(getString(R.string.chronicle_font_prefvalue)); - } - - @OnClick(R.id.radio_default) void selectDefault() { - switchFont(getString(R.string.default_font_prefvalue)); - } - - @OnClick(R.id.radio_gotham) void selectGotham() { - switchFont(getString(R.string.gotham_narrow_font_prefvalue)); - } - - @OnClick(R.id.radio_noto_sans) void selectNotoSans() { - switchFont(getString(R.string.noto_sans_font_prefvalue)); - } - - @OnClick(R.id.radio_noto_serif) void selectNotoSerif() { - switchFont(getString(R.string.noto_serif_font_prefvalue)); - } - - @OnClick(R.id.radio_open_sans_condensed) void selectOpenSansCondensed() { - switchFont(getString(R.string.open_sans_condensed_font_prefvalue)); - } - - @OnClick(R.id.radio_whitney) void selectWhitney() { - switchFont(getString(R.string.whitney_font_prefvalue)); - } -} +} \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java index de55725c8..021f075e6 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java @@ -13,6 +13,8 @@ import android.graphics.drawable.GradientDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.text.TextUtils; import android.util.Log; @@ -26,22 +28,17 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.webkit.WebView.HitTestResult; -import android.widget.Button; import android.widget.ImageView; import android.widget.PopupMenu; -import android.widget.ScrollView; import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; - import com.newsblur.R; import com.newsblur.activity.FeedItemsList; import com.newsblur.activity.NbActivity; import com.newsblur.activity.Reading; +import com.newsblur.databinding.FragmentReadingitemBinding; +import com.newsblur.databinding.IncludeReadingItemCommentBinding; import com.newsblur.domain.Classifier; -import com.newsblur.domain.Feed; import com.newsblur.domain.Story; import com.newsblur.domain.UserDetails; import com.newsblur.service.OriginalTextService; @@ -54,14 +51,9 @@ import com.newsblur.util.PrefsUtils; import com.newsblur.util.StoryUtils; import com.newsblur.util.UIUtils; import com.newsblur.util.ViewUtils; -import com.newsblur.view.FlowLayout; -import com.newsblur.view.NewsblurWebview; import com.newsblur.view.ReadingScrollView; -import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -76,24 +68,12 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI private LayoutInflater inflater; private String feedColor, feedTitle, feedFade, feedBorder, feedIconUrl, faviconText; private Classifier classifier; - @Bind(R.id.reading_webview) NewsblurWebview web; - @Bind(R.id.custom_view_container) ViewGroup webviewCustomViewLayout; - @Bind(R.id.reading_scrollview) ScrollView fragmentScrollview; private BroadcastReceiver textSizeReceiver, readingFontReceiver; - @Bind(R.id.reading_item_title) TextView itemTitle; - @Bind(R.id.reading_item_authors) TextView itemAuthors; - @Bind(R.id.reading_feed_title) TextView itemFeed; private boolean displayFeedDetails; - @Bind(R.id.reading_item_tags) FlowLayout tagContainer; private View view; private UserDetails user; private DefaultFeedView selectedFeedView; private boolean textViewUnavailable; - @Bind(R.id.reading_textloading) TextView textViewLoadingMsg; - @Bind(R.id.reading_textmodefailed) TextView textViewLoadingFailedMsg; - @Bind(R.id.save_story_button) Button saveButton; - @Bind(R.id.share_story_button) Button shareButton; - @Bind(R.id.story_context_menu_button) Button menuButton; /** The story HTML, as provided by the 'content' element of the stories API. */ private String storyContent; @@ -115,6 +95,9 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI private final Object WEBVIEW_CONTENT_MUTEX = new Object(); + private FragmentReadingitemBinding binding; + private IncludeReadingItemCommentBinding itemCommentBinding; + public static ReadingItemFragment newInstance(Story story, String feedTitle, String feedFaviconColor, String feedFaviconFade, String feedFaviconBorder, String faviconText, String faviconUrl, Classifier classifier, boolean displayFeedDetails, String sourceUserId) { ReadingItemFragment readingFragment = new ReadingItemFragment(); @@ -168,8 +151,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - int heightm = fragmentScrollview.getChildAt(0).getMeasuredHeight(); - int pos = fragmentScrollview.getScrollY(); + int heightm = binding.readingScrollview.getChildAt(0).getMeasuredHeight(); + int pos = binding.readingScrollview.getScrollY(); outState.putFloat(BUNDLE_SCROLL_POS_REL, (((float)pos)/heightm)); } @@ -177,7 +160,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI public void onDestroy() { getActivity().unregisterReceiver(textSizeReceiver); getActivity().unregisterReceiver(readingFontReceiver); - web.setOnTouchListener(null); + binding.readingWebview.setOnTouchListener(null); view.setOnTouchListener(null); getActivity().getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(null); super.onDestroy(); @@ -187,7 +170,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI // state into the webview so it behaves. @Override public void onPause() { - if (this.web != null ) { this.web.onPause(); } + if (this.binding.readingWebview != null ) { this.binding.readingWebview.onPause(); } super.onPause(); } @@ -195,25 +178,26 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI public void onResume() { super.onResume(); reloadStoryContent(); - if (this.web != null ) { this.web.onResume(); } + if (this.binding.readingWebview != null ) { this.binding.readingWebview.onResume(); } } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { this.inflater = inflater; view = inflater.inflate(R.layout.fragment_readingitem, null); - ButterKnife.bind(this, view); + binding = FragmentReadingitemBinding.bind(view); + itemCommentBinding = IncludeReadingItemCommentBinding.bind(binding.getRoot()); Reading activity = (Reading) getActivity(); fs = activity.getFeedSet(); selectedFeedView = PrefsUtils.getDefaultViewModeForFeed(activity, story.feedId); - registerForContextMenu(web); - web.setCustomViewLayout(webviewCustomViewLayout); - web.setWebviewWrapperLayout(fragmentScrollview); - web.setBackgroundColor(Color.TRANSPARENT); - web.fragment = this; - web.activity = activity; + registerForContextMenu(binding.readingWebview); + binding.readingWebview.setCustomViewLayout(binding.customViewContainer); + binding.readingWebview.setWebviewWrapperLayout(binding.readingScrollview); + binding.readingWebview.setBackgroundColor(Color.TRANSPARENT); + binding.readingWebview.fragment = this; + binding.readingWebview.activity = activity; setupItemMetadata(); updateShareButton(); @@ -228,6 +212,29 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI return view; } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.storyContextMenuButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onClickMenuButton(); + } + }); + itemCommentBinding.saveStoryButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + clickSave(); + } + }); + itemCommentBinding.shareStoryButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + clickShare(); + } + }); + } + private void setupImmersiveViewGestureDetector() { // Change the system visibility on the decorview from the activity so that the state is maintained as we page through // fragments @@ -239,7 +246,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI return gestureDetector.onTouchEvent(motionEvent); } }; - web.setOnTouchListener(touchListener); + binding.readingWebview.setOnTouchListener(touchListener); view.setOnTouchListener(touchListener); getActivity().getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(immersiveViewHandler); @@ -247,7 +254,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - HitTestResult result = web.getHitTestResult(); + HitTestResult result = binding.readingWebview.getHitTestResult(); if (result.getType() == HitTestResult.IMAGE_TYPE || result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE ) { // if the long-pressed item was an image, see if we can pop up a little dialogue @@ -298,8 +305,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI } } - @OnClick(R.id.story_context_menu_button) void onClickMenuButton() { - PopupMenu pm = new PopupMenu(getActivity(), menuButton); + private void onClickMenuButton() { + PopupMenu pm = new PopupMenu(getActivity(), binding.storyContextMenuButton); Menu menu = pm.getMenu(); pm.getMenuInflater().inflate(R.menu.story_context, menu); @@ -386,7 +393,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI } } - @OnClick(R.id.save_story_button) void clickSave() { + private void clickSave() { if (story.starred) { FeedUtils.setStorySaved(story.storyHash, false, getActivity()); } else { @@ -395,24 +402,24 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI } private void updateSaveButton() { - if (saveButton == null) return; - saveButton.setText(story.starred ? R.string.unsave_this : R.string.save_this); + if (itemCommentBinding.saveStoryButton == null) return; + itemCommentBinding.saveStoryButton.setText(story.starred ? R.string.unsave_this : R.string.save_this); } - @OnClick(R.id.share_story_button) void clickShare() { + private void clickShare() { DialogFragment newFragment = ShareDialogFragment.newInstance(story, sourceUserId); newFragment.show(getFragmentManager(), "dialog"); } private void updateShareButton() { - if (shareButton == null) return; + if (itemCommentBinding.shareStoryButton == null) return; for (String userId : story.sharedUserIds) { if (TextUtils.equals(userId, user.id)) { - shareButton.setText(R.string.already_shared); + itemCommentBinding.shareStoryButton.setText(R.string.already_shared); return; } } - shareButton.setText(R.string.share_this); + itemCommentBinding.shareStoryButton.setText(R.string.share_this); } private void setupItemCommentsAndShares() { @@ -443,28 +450,28 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI feedHeaderBorder.setBackgroundColor(Color.parseColor("#" + feedBorder)); if (TextUtils.equals(faviconText, "black")) { - itemFeed.setTextColor(UIUtils.getColor(getActivity(), R.color.text)); - itemFeed.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_white)); + binding.readingFeedTitle.setTextColor(UIUtils.getColor(getActivity(), R.color.text)); + binding.readingFeedTitle.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_white)); } else { - itemFeed.setTextColor(UIUtils.getColor(getActivity(), R.color.white)); - itemFeed.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_black)); + binding.readingFeedTitle.setTextColor(UIUtils.getColor(getActivity(), R.color.white)); + binding.readingFeedTitle.setShadowLayer(1, 0, 1, UIUtils.getColor(getActivity(), R.color.half_black)); } if (!displayFeedDetails) { - itemFeed.setVisibility(View.GONE); + binding.readingFeedTitle.setVisibility(View.GONE); feedIcon.setVisibility(View.GONE); } else { FeedUtils.iconLoader.displayImage(feedIconUrl, feedIcon, 0, false); - itemFeed.setText(feedTitle); + binding.readingFeedTitle.setText(feedTitle); } itemDate.setText(StoryUtils.formatLongDate(getActivity(), story.timestamp)); if (story.tags.length <= 0) { - tagContainer.setVisibility(View.GONE); + binding.readingItemTags.setVisibility(View.GONE); } - itemAuthors.setOnClickListener(new OnClickListener() { + binding.readingItemAuthors.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (story.feedId.equals("0")) return; // cannot train on feedless stories @@ -473,7 +480,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI } }); - itemFeed.setOnClickListener(new OnClickListener() { + binding.readingFeedTitle.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (story.feedId.equals("0")) return; // cannot train on feedless stories @@ -482,7 +489,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI } }); - itemTitle.setOnClickListener(new OnClickListener() { + binding.readingItemTitle.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(Intent.ACTION_VIEW); @@ -506,7 +513,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI Drawable tag_green_background = UIUtils.getDrawable(getActivity(), R.drawable.tag_background_positive); Drawable tag_red_background = UIUtils.getDrawable(getActivity(), R.drawable.tag_background_negative); - tagContainer.removeAllViews(); + binding.readingItemTags.removeAllViews(); for (String tag : story.tags) { // TODO: these textviews with compound images are buggy, but stubbed in to let colourblind users // see what is going on. these should be replaced with proper Chips when the v28 Chip lib @@ -549,21 +556,21 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI }); } - tagContainer.addView(v); + binding.readingItemTags.addView(v); } if (!TextUtils.isEmpty(story.authors)) { - itemAuthors.setText("• " + story.authors); + binding.readingItemAuthors.setText("• " + story.authors); if (classifier != null && classifier.authors.containsKey(story.authors)) { switch (classifier.authors.get(story.authors)) { case Classifier.LIKE: - itemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.positive)); + binding.readingItemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.positive)); break; case Classifier.DISLIKE: - itemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.negative)); + binding.readingItemAuthors.setTextColor(UIUtils.getColor(getActivity(), R.color.negative)); break; default: - itemAuthors.setTextColor(UIUtils.getThemedColor(getActivity(), R.attr.readingItemMetadata, android.R.attr.textColor)); + binding.readingItemAuthors.setTextColor(UIUtils.getThemedColor(getActivity(), R.attr.readingItemMetadata, android.R.attr.textColor)); break; } } @@ -571,7 +578,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI String title = story.title; title = UIUtils.colourTitleFromClassifier(title, classifier); - itemTitle.setText(UIUtils.fromHtml(title)); + binding.readingItemTitle.setText(UIUtils.fromHtml(title)); } public void switchSelectedViewMode() { @@ -613,8 +620,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI private void reloadStoryContent() { // reset indicators - textViewLoadingMsg.setVisibility(View.GONE); - textViewLoadingFailedMsg.setVisibility(View.GONE); + binding.readingTextloading.setVisibility(View.GONE); + binding.readingTextmodefailed.setVisibility(View.GONE); enableProgress(false); boolean needStoryContent = false; @@ -623,10 +630,10 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI needStoryContent = true; } else { if (textViewUnavailable) { - textViewLoadingFailedMsg.setVisibility(View.VISIBLE); + binding.readingTextmodefailed.setVisibility(View.VISIBLE); needStoryContent = true; } else if (originalText == null) { - textViewLoadingMsg.setVisibility(View.VISIBLE); + binding.readingTextloading.setVisibility(View.VISIBLE); enableProgress(true); loadOriginalText(); // still show the story mode version, as the text mode one may take some time @@ -786,7 +793,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI builder.append("
"); builder.append(storyText); builder.append("
"); - web.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "UTF-8", null); + binding.readingWebview.loadDataWithBaseURL("file:///android_asset/", builder.toString(), "text/html", "UTF-8", null); } } @@ -887,10 +894,10 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI // insufficient time to allow the WebView to actually finish internally computing state and size. // an additional fixed delay is added in a last ditch attempt to give the black-box platform // threads a chance to finish their work. - fragmentScrollview.postDelayed(new Runnable() { + binding.readingScrollview.postDelayed(new Runnable() { public void run() { - int relPos = Math.round(fragmentScrollview.getChildAt(0).getMeasuredHeight() * savedScrollPosRel); - fragmentScrollview.scrollTo(0, relPos); + int relPos = Math.round(binding.readingScrollview.getChildAt(0).getMeasuredHeight() * savedScrollPosRel); + binding.readingScrollview.scrollTo(0, relPos); } }, 75L); } @@ -903,7 +910,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI private class TextSizeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - web.setTextSize(intent.getFloatExtra(TEXT_SIZE_VALUE, 1.0f)); + binding.readingWebview.setTextSize(intent.getFloatExtra(TEXT_SIZE_VALUE, 1.0f)); } } @@ -924,7 +931,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI @Override public boolean onSingleTapUp(MotionEvent e) { - if (web.wasLinkClicked()) { + if (binding.readingWebview.wasLinkClicked()) { // Clicked a link so ignore immersive view return super.onSingleTapUp(e); } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingPagerFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingPagerFragment.java index 4d35f82bf..419b90961 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingPagerFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingPagerFragment.java @@ -1,16 +1,13 @@ package com.newsblur.fragment; import android.os.Bundle; -import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import butterknife.ButterKnife; -import butterknife.Bind; - import com.newsblur.R; import com.newsblur.activity.Reading; +import com.newsblur.databinding.FragmentReadingpagerBinding; /* * A fragment to hold the story pager. Eventually this fragment should hold much of the UI and logic @@ -21,8 +18,6 @@ import com.newsblur.activity.Reading; */ public class ReadingPagerFragment extends NbFragment { - @Bind(R.id.reading_pager) ViewPager pager; - public static ReadingPagerFragment newInstance() { ReadingPagerFragment fragment = new ReadingPagerFragment(); Bundle arguments = new Bundle(); @@ -33,12 +28,12 @@ public class ReadingPagerFragment extends NbFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_readingpager, null); - ButterKnife.bind(this, v); + FragmentReadingpagerBinding binding = FragmentReadingpagerBinding.bind(v); Reading activity = ((Reading) getActivity()); - pager.addOnPageChangeListener(activity); - activity.offerPager(pager, getChildFragmentManager()); + binding.readingPager.addOnPageChangeListener(activity); + activity.offerPager(binding.readingPager, getChildFragmentManager()); return v; } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/RegisterProgressFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/RegisterProgressFragment.java index a73118af9..9f3308d06 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/RegisterProgressFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/RegisterProgressFragment.java @@ -3,23 +3,19 @@ package com.newsblur.fragment; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; -import android.widget.Button; -import android.widget.ImageView; import android.widget.Toast; -import android.widget.ViewSwitcher; - -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; import com.newsblur.R; import com.newsblur.activity.AddSocial; import com.newsblur.activity.Login; +import com.newsblur.databinding.FragmentRegisterprogressBinding; import com.newsblur.network.APIManager; import com.newsblur.network.domain.RegisterResponse; @@ -31,9 +27,7 @@ public class RegisterProgressFragment extends Fragment { private String password; private String email; private RegisterTask registerTask; - @Bind(R.id.register_viewswitcher) ViewSwitcher switcher; - @Bind(R.id.registering_next_1) Button next; - @Bind(R.id.registerprogress_logo) ImageView registerProgressLogo; + private FragmentRegisterprogressBinding binding; public static RegisterProgressFragment getInstance(String username, String password, String email) { RegisterProgressFragment fragment = new RegisterProgressFragment(); @@ -59,12 +53,12 @@ public class RegisterProgressFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_registerprogress, null); - ButterKnife.bind(this, v); + binding = FragmentRegisterprogressBinding.bind(v); - registerProgressLogo.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.rotate)); + binding.registerprogressLogo.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.rotate)); if (registerTask != null) { - switcher.showNext(); + binding.registerViewswitcher.showNext(); } else { registerTask = new RegisterTask(); registerTask.execute(); @@ -73,7 +67,18 @@ public class RegisterProgressFragment extends Fragment { return v; } - @OnClick(R.id.registering_next_1) void next() { + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.registeringNext1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + next(); + } + }); + } + + private void next() { Intent i = new Intent(getActivity(), AddSocial.class); startActivity(i); } @@ -88,7 +93,7 @@ public class RegisterProgressFragment extends Fragment { @Override protected void onPostExecute(RegisterResponse response) { if (response.authenticated) { - switcher.showNext(); + binding.registerViewswitcher.showNext(); } else { String errorMessage = response.getErrorMessage(); if(errorMessage == null) { diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/RenameDialogFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/RenameDialogFragment.java new file mode 100644 index 000000000..52a9f09a2 --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/RenameDialogFragment.java @@ -0,0 +1,106 @@ +package com.newsblur.fragment; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Toast; + +import com.newsblur.R; +import com.newsblur.databinding.DialogRenameBinding; +import com.newsblur.domain.Feed; +import com.newsblur.network.APIManager; +import com.newsblur.util.AppConstants; +import com.newsblur.util.FeedUtils; + +public class RenameDialogFragment extends DialogFragment { + + private static final String FEED = "feed"; + private static final String FOLDER = "folder"; + private static final String FOLDER_NAME = "folder_name"; + private static final String FOLDER_PARENT = "folder_parent"; + private static final String RENAME_TYPE = "rename_type"; + + public static RenameDialogFragment newInstance(Feed feed) { + RenameDialogFragment fragment = new RenameDialogFragment(); + Bundle args = new Bundle(); + args.putSerializable(FEED, feed); + args.putString(RENAME_TYPE, FEED); + fragment.setArguments(args); + return fragment; + } + + public static RenameDialogFragment newInstance(String folderName, String folderParent) { + RenameDialogFragment fragment = new RenameDialogFragment(); + Bundle args = new Bundle(); + args.putString(FOLDER_NAME, folderName); + args.putString(FOLDER_PARENT, folderParent); + args.putString(RENAME_TYPE, FOLDER); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Activity activity = getActivity(); + LayoutInflater inflater = LayoutInflater.from(activity); + View v = inflater.inflate(R.layout.dialog_rename, null); + final DialogRenameBinding binding = DialogRenameBinding.bind(v); + + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setView(v); + builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + RenameDialogFragment.this.dismiss(); + } + }); + if (getArguments().getString(RENAME_TYPE).equals(FEED)) { + final Feed feed = (Feed) getArguments().getSerializable(FEED); + builder.setTitle(String.format(getResources().getString(R.string.title_rename_feed), feed.title)); + binding.inputName.setText(feed.title); + builder.setPositiveButton(R.string.feed_name_save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + FeedUtils.renameFeed(activity, feed.feedId, binding.inputName.getText().toString()); + RenameDialogFragment.this.dismiss(); + } + }); + } else { // FOLDER + final String folderName = getArguments().getString(FOLDER_NAME); + final String folderParentName = getArguments().getString(FOLDER_PARENT); + + builder.setTitle(String.format(getResources().getString(R.string.title_rename_folder), folderName)); + binding.inputName.setText(folderName); + + builder.setPositiveButton(R.string.folder_name_save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + String newFolderName = binding.inputName.getText().toString().toUpperCase(); + if (TextUtils.isEmpty(newFolderName)) { + Toast.makeText(activity, R.string.add_folder_name, Toast.LENGTH_SHORT).show(); + return; + } + + String inFolder = ""; + if (!TextUtils.isEmpty(folderParentName) && !folderParentName.equals(AppConstants.ROOT_FOLDER)) { + inFolder = folderParentName; + } + FeedUtils.renameFolder(folderName, newFolderName, inFolder, activity, new APIManager(activity)); + RenameDialogFragment.this.dismiss(); + } + }); + } + + return builder.create(); + } +} diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/RenameFeedFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/RenameFeedFragment.java deleted file mode 100644 index caf4641f2..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/RenameFeedFragment.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.newsblur.fragment; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; - -import butterknife.ButterKnife; -import butterknife.Bind; - -import com.newsblur.R; -import com.newsblur.domain.Feed; -import com.newsblur.util.FeedUtils; - -public class RenameFeedFragment extends DialogFragment { - - private Feed feed; - - @Bind(R.id.feed_name_field) EditText feedNameView; - - public static RenameFeedFragment newInstance(Feed feed) { - RenameFeedFragment fragment = new RenameFeedFragment(); - Bundle args = new Bundle(); - args.putSerializable("feed", feed); - fragment.setArguments(args); - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - feed = (Feed) getArguments().getSerializable("feed"); - - final Activity activity = getActivity(); - LayoutInflater inflater = LayoutInflater.from(activity); - View v = inflater.inflate(R.layout.dialog_rename_feed, null); - ButterKnife.bind(this, v); - - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(String.format(getResources().getString(R.string.title_rename_feed), feed.title)); - builder.setView(v); - - feedNameView.setText(feed.title); - - builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - RenameFeedFragment.this.dismiss(); - } - }); - builder.setPositiveButton(R.string.feed_name_save, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - FeedUtils.renameFeed(activity, feed.feedId, feedNameView.getText().toString()); - RenameFeedFragment.this.dismiss(); - } - }); - - Dialog dialog = builder.create(); - return dialog; - } - -} - diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/SaveSearchFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/SaveSearchFragment.java new file mode 100644 index 000000000..305e9bbc9 --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/SaveSearchFragment.java @@ -0,0 +1,48 @@ +package com.newsblur.fragment; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; + +import com.newsblur.R; +import com.newsblur.network.APIManager; +import com.newsblur.util.FeedUtils; + +public class SaveSearchFragment extends DialogFragment { + + private static final String FEED_ID = "feed_id"; + private static final String QUERY = "query"; + + public static SaveSearchFragment newInstance(String feedId, String query) { + SaveSearchFragment frag = new SaveSearchFragment(); + Bundle args = new Bundle(); + args.putString(FEED_ID, feedId); + args.putString(QUERY, query); + frag.setArguments(args); + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(String.format(getResources().getString(R.string.add_saved_search_message), getArguments().getString(QUERY))); + builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + FeedUtils.saveSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity(), new APIManager(getActivity())); + SaveSearchFragment.this.dismiss(); + } + }); + builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + SaveSearchFragment.this.dismiss(); + } + }); + return builder.create(); + } +} \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/StoryIntelTrainerFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/StoryIntelTrainerFragment.java index 749547222..135dc4b8a 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/StoryIntelTrainerFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/StoryIntelTrainerFragment.java @@ -14,20 +14,15 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.LinearLayout; import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.Bind; - import com.newsblur.R; +import com.newsblur.databinding.DialogTrainstoryBinding; import com.newsblur.domain.Classifier; import com.newsblur.domain.Story; import com.newsblur.util.FeedSet; import com.newsblur.util.FeedUtils; import com.newsblur.util.UIUtils; -import com.newsblur.view.SelectOnlyEditText; public class StoryIntelTrainerFragment extends DialogFragment { @@ -35,17 +30,7 @@ public class StoryIntelTrainerFragment extends DialogFragment { private FeedSet fs; private Classifier classifier; private Integer newTitleTraining; - - @Bind(R.id.intel_tag_header) TextView headerTags; - @Bind(R.id.intel_author_header) TextView headerAuthor; - @Bind(R.id.intel_title_selection) SelectOnlyEditText titleSelection; - @Bind(R.id.intel_title_like) Button titleLikeButton; - @Bind(R.id.intel_title_dislike) Button titleDislikeButton; - @Bind(R.id.intel_title_clear) Button titleClearButton; - @Bind(R.id.existing_title_intel_container) LinearLayout titleRowsContainer; - @Bind(R.id.existing_tag_intel_container) LinearLayout tagRowsContainer; - @Bind(R.id.existing_author_intel_container) LinearLayout authorRowsContainer; - @Bind(R.id.existing_feed_intel_container) LinearLayout feedRowsContainer; + private DialogTrainstoryBinding binding; public static StoryIntelTrainerFragment newInstance(Story story, FeedSet fs) { if (story.feedId.equals("0")) { @@ -69,44 +54,44 @@ public class StoryIntelTrainerFragment extends DialogFragment { final Activity activity = getActivity(); LayoutInflater inflater = LayoutInflater.from(activity); View v = inflater.inflate(R.layout.dialog_trainstory, null); - ButterKnife.bind(this, v); + binding = DialogTrainstoryBinding.bind(v); // set up the special title training box for the title from this story and the associated buttons - titleSelection.setText(story.title); + binding.intelTitleSelection.setText(story.title); // the layout sets inputType="none" on this EditText, but a widespread platform bug requires us // to also set this programmatically to make the field read-only for selection. - titleSelection.setInputType(InputType.TYPE_NULL); + binding.intelTitleSelection.setInputType(InputType.TYPE_NULL); // the user is selecting for our custom widget, not to copy/paste - titleSelection.disableActionMenu(); + binding.intelTitleSelection.disableActionMenu(); // pre-select the whole title to make it easier for the user to manipulate the selection handles - titleSelection.selectAll(); + binding.intelTitleSelection.selectAll(); // do this after init and selection to prevent toast spam - titleSelection.setForceSelection(true); + binding.intelTitleSelection.setForceSelection(true); // the disposition buttons for a new title training don't immediately impact the classifier object, // lest the user want to change selection substring after choosing the disposition. so just store // the training factor in a variable that can be pulled on completion - titleLikeButton.setOnClickListener(new OnClickListener() { + binding.intelTitleLike.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { newTitleTraining = Classifier.LIKE; - titleLikeButton.setBackgroundResource(R.drawable.ic_like_active); - titleDislikeButton.setBackgroundResource(R.drawable.ic_dislike_gray55); + binding.intelTitleLike.setBackgroundResource(R.drawable.ic_like_active); + binding.intelTitleDislike.setBackgroundResource(R.drawable.ic_dislike_gray55); } }); - titleDislikeButton.setOnClickListener(new OnClickListener() { + binding.intelTitleDislike.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { newTitleTraining = Classifier.DISLIKE; - titleLikeButton.setBackgroundResource(R.drawable.ic_like_gray55); - titleDislikeButton.setBackgroundResource(R.drawable.ic_dislike_active); + binding.intelTitleLike.setBackgroundResource(R.drawable.ic_like_gray55); + binding.intelTitleDislike.setBackgroundResource(R.drawable.ic_dislike_active); } }); - titleClearButton.setOnClickListener(new OnClickListener() { + binding.intelTitleClear.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { newTitleTraining = null; - titleLikeButton.setBackgroundResource(R.drawable.ic_like_gray55); - titleDislikeButton.setBackgroundResource(R.drawable.ic_dislike_gray55); + binding.intelTitleLike.setBackgroundResource(R.drawable.ic_like_gray55); + binding.intelTitleDislike.setBackgroundResource(R.drawable.ic_dislike_gray55); } }); @@ -117,7 +102,7 @@ public class StoryIntelTrainerFragment extends DialogFragment { TextView label = (TextView) row.findViewById(R.id.intel_row_label); label.setText(rule.getKey()); UIUtils.setupIntelDialogRow(row, classifier.title, rule.getKey()); - titleRowsContainer.addView(row); + binding.existingTitleIntelContainer.addView(row); } } @@ -127,9 +112,9 @@ public class StoryIntelTrainerFragment extends DialogFragment { TextView label = (TextView) row.findViewById(R.id.intel_row_label); label.setText(tag); UIUtils.setupIntelDialogRow(row, classifier.tags, tag); - tagRowsContainer.addView(row); + binding.existingTagIntelContainer.addView(row); } - if (story.tags.length < 1) headerTags.setVisibility(View.GONE); + if (story.tags.length < 1) binding.intelTagHeader.setVisibility(View.GONE); // there is a single author per story if (!TextUtils.isEmpty(story.authors)) { @@ -137,9 +122,9 @@ public class StoryIntelTrainerFragment extends DialogFragment { TextView labelAuthor = (TextView) rowAuthor.findViewById(R.id.intel_row_label); labelAuthor.setText(story.authors); UIUtils.setupIntelDialogRow(rowAuthor, classifier.authors, story.authors); - authorRowsContainer.addView(rowAuthor); + binding.existingAuthorIntelContainer.addView(rowAuthor); } else { - headerAuthor.setVisibility(View.GONE); + binding.intelAuthorHeader.setVisibility(View.GONE); } // there is a single feed to be trained, but it is a bit odd in that the label is the title and @@ -148,7 +133,7 @@ public class StoryIntelTrainerFragment extends DialogFragment { TextView labelFeed = (TextView) rowFeed.findViewById(R.id.intel_row_label); labelFeed.setText(FeedUtils.getFeedTitle(story.feedId)); UIUtils.setupIntelDialogRow(rowFeed, classifier.feeds, story.feedId); - feedRowsContainer.addView(rowFeed); + binding.existingFeedIntelContainer.addView(rowFeed); AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.story_intel_dialog_title); @@ -163,8 +148,8 @@ public class StoryIntelTrainerFragment extends DialogFragment { builder.setPositiveButton(R.string.dialog_story_intel_save, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - if ((newTitleTraining != null) && (!TextUtils.isEmpty(titleSelection.getSelection()))) { - classifier.title.put(titleSelection.getSelection(), newTitleTraining); + if ((newTitleTraining != null) && (!TextUtils.isEmpty(binding.intelTitleSelection.getSelection()))) { + classifier.title.put(binding.intelTitleSelection.getSelection(), newTitleTraining); } FeedUtils.updateClassifier(story.feedId, classifier, fs, activity); StoryIntelTrainerFragment.this.dismiss(); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/StoryOrderDialogFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/StoryOrderDialogFragment.java index 2ffc2c68c..2ae1ef3b9 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/StoryOrderDialogFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/StoryOrderDialogFragment.java @@ -7,13 +7,9 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; -import android.widget.RadioButton; - -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; import com.newsblur.R; +import com.newsblur.databinding.StoryorderDialogBinding; import com.newsblur.util.StoryOrder; import com.newsblur.util.StoryOrderChangedListener; @@ -21,8 +17,6 @@ public class StoryOrderDialogFragment extends DialogFragment { private static String CURRENT_ORDER = "currentOrder"; private StoryOrder currentValue; - @Bind(R.id.radio_newest) RadioButton newestButton; - @Bind(R.id.radio_oldest) RadioButton oldestButton; public static StoryOrderDialogFragment newInstance(StoryOrder currentValue) { StoryOrderDialogFragment dialog = new StoryOrderDialogFragment(); @@ -42,25 +36,38 @@ public class StoryOrderDialogFragment extends DialogFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { currentValue = (StoryOrder) getArguments().getSerializable(CURRENT_ORDER); View v = inflater.inflate(R.layout.storyorder_dialog, null); - ButterKnife.bind(this, v); + StoryorderDialogBinding binding = StoryorderDialogBinding.bind(v); - newestButton.setChecked(currentValue == StoryOrder.NEWEST); - oldestButton.setChecked(currentValue == StoryOrder.OLDEST); + binding.radioNewest.setChecked(currentValue == StoryOrder.NEWEST); + binding.radioOldest.setChecked(currentValue == StoryOrder.OLDEST); getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); getDialog().getWindow().getAttributes().gravity = Gravity.BOTTOM; + + binding.radioNewest.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selectNewest(); + } + }); + binding.radioOldest.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selectOldest(); + } + }); return v; } - @OnClick(R.id.radio_newest) void selectNewest() { + private void selectNewest() { if (currentValue != StoryOrder.NEWEST) { ((StoryOrderChangedListener) getActivity()).storyOrderChanged(StoryOrder.NEWEST); } dismiss(); } - @OnClick(R.id.radio_oldest) void selectOldest() { + private void selectOldest() { if (currentValue != StoryOrder.OLDEST) { ((StoryOrderChangedListener) getActivity()).storyOrderChanged(StoryOrder.OLDEST); } diff --git a/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java b/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java index 1f53de93c..c87d92087 100644 --- a/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java +++ b/clients/android/NewsBlur/src/com/newsblur/network/APIConstants.java @@ -71,6 +71,11 @@ public class APIConstants { public static final String PATH_SET_NOTIFICATIONS = "/notifications/feed/"; public static final String PATH_INSTA_FETCH = "/rss_feeds/exception_retry"; public static final String PATH_RENAME_FEED = "/reader/rename_feed"; + public static final String PATH_DELETE_SEARCH = "/reader/delete_search"; + public static final String PATH_SAVE_SEARCH = "/reader/save_search"; + public static final String PATH_ADD_FOLDER = "/reader/add_folder"; + public static final String PATH_DELETE_FOLDER = "/reader/delete_folder"; + public static final String PATH_RENAME_FOLDER = "/reader/rename_folder"; public static String buildUrl(String path) { return CurrentUrlBase + path; @@ -121,6 +126,9 @@ public class APIConstants { public static final String PARAMETER_RESET_FETCH = "reset_fetch"; public static final String PARAMETER_INFREQUENT = "infrequent"; public static final String PARAMETER_FEEDTITLE = "feed_title"; + public static final String PARAMETER_FOLDER_TO_DELETE = "folder_to_delete"; + public static final String PARAMETER_FOLDER_TO_RENAME = "folder_to_rename"; + public static final String PARAMETER_NEW_FOLDER_NAME = "new_folder_name"; public static final String VALUE_PREFIX_SOCIAL = "social:"; public static final String VALUE_ALLSOCIAL = "river:blurblogs"; // the magic value passed to the mark-read API for all social feeds diff --git a/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java b/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java index 858bd0a27..2a3d66176 100644 --- a/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java +++ b/clients/android/NewsBlur/src/com/newsblur/network/APIManager.java @@ -15,6 +15,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; @@ -550,11 +551,21 @@ public class APIManager { return (CommentResponse) response.getResponse(gson, CommentResponse.class); } - public AddFeedResponse addFeed(String feedUrl) { + public NewsBlurResponse addFolder(String folderName) { + ContentValues values = new ContentValues(); + values.put(APIConstants.PARAMETER_FOLDER, folderName); + APIResponse response = post(buildUrl(APIConstants.PATH_ADD_FOLDER), values); + return response.getResponse(gson, NewsBlurResponse.class); + } + + public AddFeedResponse addFeed(String feedUrl, @Nullable String folderName) { ContentValues values = new ContentValues(); values.put(APIConstants.PARAMETER_URL, feedUrl); + if (!TextUtils.isEmpty(folderName)) { + values.put(APIConstants.PARAMETER_FOLDER, folderName); + } APIResponse response = post(buildUrl(APIConstants.PATH_ADD_FEED), values); - return (AddFeedResponse) response.getResponse(gson, AddFeedResponse.class); + return response.getResponse(gson, AddFeedResponse.class); } public FeedResult[] searchForFeed(String searchTerm) { @@ -579,6 +590,30 @@ public class APIManager { return response.getResponse(gson, NewsBlurResponse.class); } + public NewsBlurResponse deleteFolder(String folderName, String inFolder) { + ContentValues values = new ContentValues(); + values.put(APIConstants.PARAMETER_FOLDER_TO_DELETE, folderName); + values.put(APIConstants.PARAMETER_IN_FOLDER, inFolder); + APIResponse response = post(buildUrl(APIConstants.PATH_DELETE_FOLDER), values); + return response.getResponse(gson, NewsBlurResponse.class); + } + + public NewsBlurResponse deleteSearch(String feedId, String query) { + ContentValues values = new ContentValues(); + values.put(APIConstants.PARAMETER_FEEDID, feedId); + values.put(APIConstants.PARAMETER_QUERY, query); + APIResponse response = post(buildUrl(APIConstants.PATH_DELETE_SEARCH), values); + return response.getResponse(gson, NewsBlurResponse.class); + } + + public NewsBlurResponse saveSearch(String feedId, String query) { + ContentValues values = new ContentValues(); + values.put(APIConstants.PARAMETER_FEEDID, feedId); + values.put(APIConstants.PARAMETER_QUERY, query); + APIResponse response = post(buildUrl(APIConstants.PATH_SAVE_SEARCH), values); + return response.getResponse(gson, NewsBlurResponse.class); + } + public NewsBlurResponse saveFeedChooser(Set feeds) { ValueMultimap values = new ValueMultimap(); for (String feed : feeds) { @@ -617,6 +652,15 @@ public class APIManager { return response.getResponse(gson, NewsBlurResponse.class); } + public NewsBlurResponse renameFolder(String folderName, String newFolderName, String inFolder) { + ContentValues values = new ContentValues(); + values.put(APIConstants.PARAMETER_FOLDER_TO_RENAME, folderName); + values.put(APIConstants.PARAMETER_NEW_FOLDER_NAME, newFolderName); + values.put(APIConstants.PARAMETER_IN_FOLDER, inFolder); + APIResponse response = post(buildUrl(APIConstants.PATH_RENAME_FOLDER), values); + return response.getResponse(gson, NewsBlurResponse.class); + } + /* HTTP METHODS */ private APIResponse get(final String urlString) { diff --git a/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java b/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java index a1f4c9050..01b04f049 100644 --- a/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java +++ b/clients/android/NewsBlur/src/com/newsblur/network/domain/FeedFolderResponse.java @@ -15,6 +15,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.newsblur.domain.Feed; import com.newsblur.domain.Folder; +import com.newsblur.domain.SavedSearch; import com.newsblur.domain.SocialFeed; import com.newsblur.domain.StarredCount; import com.newsblur.util.AppConstants; @@ -30,6 +31,7 @@ public class FeedFolderResponse { public Set feeds; public Set socialFeeds; public Set starredCounts; + public Set savedSearches; public boolean isAuthenticated; public boolean isPremium; @@ -109,6 +111,16 @@ public class FeedFolderResponse { } } + savedSearches = new HashSet<>(); + JsonArray savedSearchesArray = (JsonArray) asJsonObject.get("saved_searches"); + if (savedSearchesArray != null) { + for (int i=0; i { // any characters we don't want in the short description, such as newlines or placeholders private final static Pattern ShortContentExcludes = Pattern.compile("[\\uFFFC\\u000A\\u000B\\u000C\\u000D]"); + private final static Pattern httpSniff = Pattern.compile("(?:http):\\/\\/"); public StoryTypeAdapter() { this.gson = new GsonBuilder() @@ -39,6 +40,14 @@ public class StoryTypeAdapter implements JsonDeserializer { // Convert story_timestamp to milliseconds story.timestamp = story.timestamp * 1000; + + // replace http image urls with https + if (httpSniff.matcher(story.content).find() && story.secureImageUrls != null && story.secureImageUrls.size() > 0) { + for (String httpUrl : story.secureImageUrls.keySet()) { + String httpsUrl = story.secureImageUrls.get(httpUrl); + story.content = story.content.replace(httpUrl, httpsUrl); + } + } // populate the shortContent field if (story.content != null) { diff --git a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java index 07e0eb657..361538c7b 100644 --- a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java +++ b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java @@ -16,6 +16,7 @@ import static com.newsblur.database.BlurDatabaseHelper.closeQuietly; import com.newsblur.database.DatabaseConstants; import com.newsblur.domain.Feed; import com.newsblur.domain.Folder; +import com.newsblur.domain.SavedSearch; import com.newsblur.domain.SocialFeed; import com.newsblur.domain.StarredCount; import com.newsblur.domain.Story; @@ -586,6 +587,12 @@ public class NBSyncService extends JobService { for (StarredCount sc : feedResponse.starredCounts) { starredCountValues.add(sc.getValues()); } + + // saved searches table + List savedSearchesValues = new ArrayList<>(); + for (SavedSearch savedSearch : feedResponse.savedSearches) { + savedSearchesValues.add(savedSearch.getValues()); + } // the API vends the starred total as a different element, roll it into // the starred counts table using a special tag StarredCount totalStarred = new StarredCount(); @@ -593,7 +600,7 @@ public class NBSyncService extends JobService { totalStarred.tag = StarredCount.TOTAL_STARRED; starredCountValues.add(totalStarred.getValues()); - dbHelper.setFeedsFolders(folderValues, feedValues, socialFeedValues, starredCountValues); + dbHelper.setFeedsFolders(folderValues, feedValues, socialFeedValues, starredCountValues, savedSearchesValues); lastFFWriteMillis = System.currentTimeMillis() - startTime; lastFeedCount = feedValues.size(); diff --git a/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java b/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java index 1a65eb85c..30c90bad4 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/FeedSet.java @@ -34,6 +34,7 @@ public class FeedSet implements Serializable { private String folderName; private String searchQuery; + private String searchFeedId; private boolean isFilterSaved = false; private boolean muted = false; @@ -75,17 +76,6 @@ public class FeedSet implements Serializable { return fs; } - /** - * Convenience constructor for multiple feeds with IDs - */ - public static FeedSet multipleFeeds(Set feedIds) { - FeedSet fs = new FeedSet(); - fs.feeds = new HashSet<>(feedIds.size()); - fs.feeds.addAll(feedIds); - fs.feeds = Collections.unmodifiableSet(fs.feeds); - return fs; - } - /** * Convenience constructor for all (non-social) feeds. */ @@ -133,6 +123,16 @@ public class FeedSet implements Serializable { return fs; } + /** + * Convenience constructor for a single saved search. + */ + public static FeedSet singleSavedSearch(String feedId, String searchQuery) { + FeedSet fs = new FeedSet(); + fs.searchQuery = searchQuery; + fs.searchFeedId = feedId; + return fs; + } + /** * Convenience constructor for global shared stories feed. */ @@ -281,6 +281,10 @@ public class FeedSet implements Serializable { return this.searchQuery; } + public String getSearchFeedId() { + return this.searchFeedId; + } + public void setFilterSaved(boolean isFilterSaved) { this.isFilterSaved = isFilterSaved; } diff --git a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java index d5bc12eaa..070465bfe 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java @@ -9,6 +9,7 @@ import java.util.Set; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.newsblur.R; @@ -18,6 +19,7 @@ import com.newsblur.domain.Classifier; import com.newsblur.domain.Feed; import com.newsblur.domain.Folder; import com.newsblur.domain.SocialFeed; +import com.newsblur.domain.StarredCount; import com.newsblur.domain.Story; import com.newsblur.fragment.ReadingActionConfirmationFragment; import com.newsblur.network.APIManager; @@ -109,6 +111,40 @@ public class FeedUtils { }.execute(); } + public static void deleteSavedSearch(final String feedId, final String query, final Context context, final APIManager apiManager) { + new AsyncTask() { + @Override + protected NewsBlurResponse doInBackground(Void... voids) { + return apiManager.deleteSearch(feedId, query); + } + + @Override + protected void onPostExecute(NewsBlurResponse newsBlurResponse) { + if (!newsBlurResponse.isError()) { + dbHelper.deleteSavedSearch(feedId, query); + NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA); + } + } + }.execute(); + } + + public static void saveSearch(final String feedId, final String query, final Context context, final APIManager apiManager) { + new AsyncTask() { + @Override + protected NewsBlurResponse doInBackground(Void... voids) { + return apiManager.saveSearch(feedId, query); + } + + @Override + protected void onPostExecute(NewsBlurResponse newsBlurResponse) { + if (!newsBlurResponse.isError()) { + NBSyncService.forceFeedsFolders(); + triggerSync(context); + } + } + }.execute(); + } + public static void deleteFeed(final String feedId, final String folderName, final Context context, final APIManager apiManager) { new AsyncTask() { @Override @@ -140,6 +176,42 @@ public class FeedUtils { }.execute(); } + public static void deleteFolder(final String folderName, final String inFolder, final Context context, final APIManager apiManager) { + new AsyncTask() { + @Override + protected NewsBlurResponse doInBackground(Void... voids) { + return apiManager.deleteFolder(folderName, inFolder); + } + + @Override + protected void onPostExecute(NewsBlurResponse result) { + super.onPostExecute(result); + if (!result.isError()) { + NBSyncService.forceFeedsFolders(); + triggerSync(context); + } + } + }.execute(); + } + + public static void renameFolder(final String folderName, final String newFolderName, final String inFolder, final Context context, final APIManager apiManager) { + new AsyncTask() { + @Override + protected NewsBlurResponse doInBackground(Void... voids) { + return apiManager.renameFolder(folderName, newFolderName, inFolder); + } + + @Override + protected void onPostExecute(NewsBlurResponse result) { + super.onPostExecute(result); + if (!result.isError()) { + NBSyncService.forceFeedsFolders(); + triggerSync(context); + } + } + }.execute(); + } + public static void markStoryUnread(final Story story, final Context context) { new AsyncTask() { @Override @@ -518,4 +590,8 @@ public class FeedUtils { return dbHelper.getSocialFeed(feedId); } + @Nullable + public static StarredCount getStarredFeedByTag(String feedId) { + return dbHelper.getStarredFeedByTag(feedId); + } } diff --git a/clients/android/NewsBlur/src/com/newsblur/view/StateToggleButton.java b/clients/android/NewsBlur/src/com/newsblur/view/StateToggleButton.java index 3b4d466f8..ad03a5db9 100644 --- a/clients/android/NewsBlur/src/com/newsblur/view/StateToggleButton.java +++ b/clients/android/NewsBlur/src/com/newsblur/view/StateToggleButton.java @@ -5,14 +5,10 @@ import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.LinearLayout; -import butterknife.ButterKnife; -import butterknife.Bind; -import butterknife.OnClick; - import com.newsblur.R; +import com.newsblur.databinding.StateToggleBinding; import com.newsblur.util.StateFilter; import com.newsblur.util.UIUtils; @@ -27,52 +23,53 @@ public class StateToggleButton extends LinearLayout { private int parentWidthPX = 0; - @Bind(R.id.toggle_all) ViewGroup allButton; - @Bind(R.id.toggle_all_icon) View allButtonIcon; - @Bind(R.id.toggle_all_text) View allButtonText; - @Bind(R.id.toggle_some) ViewGroup someButton; - @Bind(R.id.toggle_some_icon) View someButtonIcon; - @Bind(R.id.toggle_some_text) View someButtonText; - @Bind(R.id.toggle_focus) ViewGroup focusButton; - @Bind(R.id.toggle_focus_icon) View focusButtonIcon; - @Bind(R.id.toggle_focus_text) View focusButtonText; - @Bind(R.id.toggle_saved) ViewGroup savedButton; - @Bind(R.id.toggle_saved_icon) View savedButtonIcon; - @Bind(R.id.toggle_saved_text) View savedButtonText; + private StateToggleBinding binding; public StateToggleButton(Context context, AttributeSet art) { super(context, art); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.state_toggle, this); - ButterKnife.bind(this, view); + binding = StateToggleBinding.bind(view); // smooth layout transitions are enabled in our layout XML; this smooths out toggle // transitions on newer devices - allButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); - someButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); - focusButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); - savedButton.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + binding.toggleAll.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + binding.toggleSome.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + binding.toggleFocus.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + binding.toggleSaved.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); setState(state); + + binding.toggleAll.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setState(StateFilter.ALL); + } + }); + binding.toggleSome.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setState(StateFilter.SOME); + } + }); + binding.toggleFocus.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setState(StateFilter.BEST); + } + }); + binding.toggleSaved.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setState(StateFilter.SAVED); + } + }); } public void setStateListener(final StateChangedListener stateChangedListener) { this.stateChangedListener = stateChangedListener; } - @OnClick({R.id.toggle_all, R.id.toggle_some, R.id.toggle_focus, R.id.toggle_saved}) - public void onClickToggle(View v) { - if (v.getId() == R.id.toggle_all) { - setState(StateFilter.ALL); - } else if (v.getId() == R.id.toggle_some) { - setState(StateFilter.SOME); - } else if (v.getId() == R.id.toggle_focus) { - setState(StateFilter.BEST); - } else if (v.getId() == R.id.toggle_saved) { - setState(StateFilter.SAVED); - } - } - public void setState(StateFilter state) { this.state = state; updateButtonStates(); @@ -93,21 +90,21 @@ public class StateToggleButton extends LinearLayout { if (widthDP > COLLAPSE_WIDTH_DP) compactMode = false; } - allButtonText.setVisibility((!compactMode || state == StateFilter.ALL) ? View.VISIBLE : View.GONE); - allButton.setEnabled(state != StateFilter.ALL); - allButtonIcon.setAlpha(state == StateFilter.ALL ? 1.0f : 0.6f); + binding.toggleAllText.setVisibility((!compactMode || state == StateFilter.ALL) ? View.VISIBLE : View.GONE); + binding.toggleAll.setEnabled(state != StateFilter.ALL); + binding.toggleAllIcon.setAlpha(state == StateFilter.ALL ? 1.0f : 0.6f); - someButtonText.setVisibility((!compactMode || state == StateFilter.SOME) ? View.VISIBLE : View.GONE); - someButton.setEnabled(state != StateFilter.SOME); - someButtonIcon.setAlpha(state == StateFilter.SOME ? 1.0f : 0.6f); + binding.toggleSomeText.setVisibility((!compactMode || state == StateFilter.SOME) ? View.VISIBLE : View.GONE); + binding.toggleSome.setEnabled(state != StateFilter.SOME); + binding.toggleSomeIcon.setAlpha(state == StateFilter.SOME ? 1.0f : 0.6f); - focusButtonText.setVisibility((!compactMode || state == StateFilter.BEST) ? View.VISIBLE : View.GONE); - focusButton.setEnabled(state != StateFilter.BEST); - focusButtonIcon.setAlpha(state == StateFilter.BEST ? 1.0f : 0.6f); + binding.toggleFocusText.setVisibility((!compactMode || state == StateFilter.BEST) ? View.VISIBLE : View.GONE); + binding.toggleFocus.setEnabled(state != StateFilter.BEST); + binding.toggleFocusIcon.setAlpha(state == StateFilter.BEST ? 1.0f : 0.6f); - savedButtonText.setVisibility((!compactMode || state == StateFilter.SAVED) ? View.VISIBLE : View.GONE); - savedButton.setEnabled(state != StateFilter.SAVED); - savedButtonIcon.setAlpha(state == StateFilter.SAVED ? 1.0f : 0.6f); + binding.toggleSavedText.setVisibility((!compactMode || state == StateFilter.SAVED) ? View.VISIBLE : View.GONE); + binding.toggleSaved.setEnabled(state != StateFilter.SAVED); + binding.toggleSavedIcon.setAlpha(state == StateFilter.SAVED ? 1.0f : 0.6f); } public interface StateChangedListener {