diff --git a/clients/android/NewsBlur/AndroidManifest.xml b/clients/android/NewsBlur/AndroidManifest.xml index 269e37a96..6c0853177 100644 --- a/clients/android/NewsBlur/AndroidManifest.xml +++ b/clients/android/NewsBlur/AndroidManifest.xml @@ -4,7 +4,6 @@ - @@ -133,19 +132,15 @@ android:name=".activity.MuteConfig" android:launchMode="singleTask" android:label="@string/mute_sites"/> - - - - - - - - + + + - + + diff --git a/clients/android/NewsBlur/res/layout/activity_feed_search.xml b/clients/android/NewsBlur/res/layout/activity_feed_search.xml index 31fbdf06a..9508472dc 100644 --- a/clients/android/NewsBlur/res/layout/activity_feed_search.xml +++ b/clients/android/NewsBlur/res/layout/activity_feed_search.xml @@ -4,31 +4,97 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + - + + + + + + + + + + + + + + + + + + - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> - + diff --git a/clients/android/NewsBlur/res/layout/fragment_readingitem.xml b/clients/android/NewsBlur/res/layout/fragment_readingitem.xml index ca2077ec8..75a093e6a 100644 --- a/clients/android/NewsBlur/res/layout/fragment_readingitem.xml +++ b/clients/android/NewsBlur/res/layout/fragment_readingitem.xml @@ -1,199 +1,197 @@ + android:layout_height="match_parent"> - + android:layout_height="match_parent"> - + android:minHeight="6dp" + android:paddingTop="4dp" + android:paddingBottom="4dp"> - + + + android:layout_gravity="center_vertical" + android:layout_marginStart="40dp" + android:ellipsize="end" + android:lines="1" + android:textSize="12sp" + android:textStyle="bold" /> + + + + + + + + + android:layout_height="wrap_content" + android:paddingBottom="8dp"> - + + + + + + + + + + android:layout_gravity="bottom" /> - + - - - - - - - - + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:paddingTop="5dp" + android:paddingBottom="5dp" + android:text="@string/orig_text_loading" + android:textSize="16sp" + android:textStyle="italic" + android:visibility="gone" /> - + - + - + - + - + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/clients/android/NewsBlur/res/layout/view_feed_search_row.xml b/clients/android/NewsBlur/res/layout/view_feed_search_row.xml new file mode 100644 index 000000000..463aaadea --- /dev/null +++ b/clients/android/NewsBlur/res/layout/view_feed_search_row.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/clients/android/NewsBlur/res/menu/search.xml b/clients/android/NewsBlur/res/menu/search.xml deleted file mode 100644 index bbe5300b0..000000000 --- a/clients/android/NewsBlur/res/menu/search.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/clients/android/NewsBlur/res/values/strings.xml b/clients/android/NewsBlur/res/values/strings.xml index 4d174c6a4..ecfb73088 100644 --- a/clients/android/NewsBlur/res/values/strings.xml +++ b/clients/android/NewsBlur/res/values/strings.xml @@ -15,7 +15,7 @@ Retrieving feeds… Next - Search for feeds + Search for feeds Loading… Fetching story text… @@ -56,7 +56,7 @@ TOP LEVEL %1$s %2$s - %1$s \n\n\"%2$s\" \n\n%3$s \n\n--\nShared with NewsBlur.com + %1$s \n\%2$s \n\n%3$s \n\n—\nShared with NewsBlur.com Comment (Optional) Share \"%s\" to your Blurblog? SHARE @@ -200,11 +200,9 @@ Hide Story Changes Story Changes Loading ... Search your feeds - Search term or feed URL Search for stories Type a search term to begin Add new feed - Search Adding feed… Please enter folder name + Add new folder diff --git a/clients/android/NewsBlur/res/xml/searchable.xml b/clients/android/NewsBlur/res/xml/searchable.xml deleted file mode 100644 index 1db09b7e5..000000000 --- a/clients/android/NewsBlur/res/xml/searchable.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/FeedSearchResultAdapter.java b/clients/android/NewsBlur/src/com/newsblur/activity/FeedSearchResultAdapter.java deleted file mode 100644 index 401cd9187..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/activity/FeedSearchResultAdapter.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.newsblur.activity; - -import java.util.List; - -import com.newsblur.R; -import com.newsblur.domain.FeedResult; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.text.TextUtils; -import android.util.Base64; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -public class FeedSearchResultAdapter extends ArrayAdapter{ - - private LayoutInflater inflater; - private Context context; - - public FeedSearchResultAdapter(Context context, int resource, int textViewResourceId, List items) { - super(context, resource, textViewResourceId, items); - this.context = context; - inflater = ((Activity) context).getLayoutInflater(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v; - if (convertView != null) { - v = convertView; - } else { - v = inflater.inflate(R.layout.row_feedresult, null); - } - - FeedResult result = getItem(position); - ImageView favicon = (ImageView) v.findViewById(R.id.row_result_feedicon); - Bitmap bitmap = null; - if (!TextUtils.isEmpty(result.favicon)) { - final byte[] data = Base64.decode(result.favicon, Base64.DEFAULT); - bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); - } - if (bitmap == null) { - bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.world); - } - - favicon.setImageBitmap(bitmap); - - ((TextView) v.findViewById(R.id.row_result_title)).setText(result.label); - ((TextView) v.findViewById(R.id.row_result_tagline)).setText(result.tagline); - if (!TextUtils.isEmpty(result.url)) { - ((TextView) v.findViewById(R.id.row_result_address)).setText(result.url); - } else { - v.findViewById(R.id.row_result_address).setVisibility(View.GONE); - } - ((TextView) v.findViewById(R.id.row_result_subscribercount)).setText(result.numberOfSubscriber + " subscribers"); - - - return v; - } - -} diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/FeedSearchResultAdapter.kt b/clients/android/NewsBlur/src/com/newsblur/activity/FeedSearchResultAdapter.kt new file mode 100644 index 000000000..c20198de4 --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/activity/FeedSearchResultAdapter.kt @@ -0,0 +1,94 @@ +package com.newsblur.activity + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.text.TextUtils +import android.util.Base64 +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.newsblur.R +import com.newsblur.databinding.ViewFeedSearchRowBinding +import com.newsblur.domain.FeedResult + +internal class FeedSearchAdapter(private val onClickListener: OnFeedSearchResultClickListener) : RecyclerView.Adapter() { + + private val resultsList: MutableList = ArrayList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.view_feed_search_row, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val result = resultsList[position] + var bitmap: Bitmap? = null + if (!TextUtils.isEmpty(result.favicon)) { + val data = Base64.decode(result.favicon, Base64.DEFAULT) + bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) + } + bitmap?.let { + holder.binding.imgFeedIcon.setImageBitmap(bitmap) + } + + holder.binding.textTitle.text = result.label + holder.binding.textTagline.text = result.tagline + val subscribersCountText = holder.binding.root.context.resources.getString(R.string.feed_subscribers, result.numberOfSubscriber) + holder.binding.textSubscriptionCount.text = subscribersCountText + + if (!TextUtils.isEmpty(result.url)) { + holder.binding.rowResultAddress.text = result.url + holder.binding.rowResultAddress.visibility = View.VISIBLE + } else { + holder.binding.rowResultAddress.visibility = View.GONE + } + + holder.itemView.setOnClickListener { + onClickListener.onFeedSearchResultClickListener(result) + } + } + + override fun getItemCount(): Int = resultsList.size + + fun replaceAll(results: Array) { + val newResultsList: List = results.toList() + val diffCallback = ResultDiffCallback(resultsList, newResultsList) + val diffResult = DiffUtil.calculateDiff(diffCallback) + resultsList.clear() + resultsList.addAll(newResultsList) + diffResult.dispatchUpdatesTo(this) + } + + internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val binding: ViewFeedSearchRowBinding = ViewFeedSearchRowBinding.bind(itemView) + } + + internal class ResultDiffCallback(private val oldList: List, + private val newList: List) : DiffUtil.Callback() { + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldFeedResult = oldList[oldItemPosition] + val newFeedResult = newList[newItemPosition] + return oldFeedResult.label == newFeedResult.label && + oldFeedResult.numberOfSubscriber == newFeedResult.numberOfSubscriber + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldFeedResult = oldList[oldItemPosition] + val newFeedResult = newList[newItemPosition] + return oldFeedResult.label == newFeedResult.label + && oldFeedResult.tagline == newFeedResult.tagline + } + + override fun getOldListSize(): Int = oldList.size + + override fun getNewListSize(): Int = newList.size + } + + interface OnFeedSearchResultClickListener { + + fun onFeedSearchResultClickListener(result: FeedResult) + } +} \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java b/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java index 7cf4c09fe..b935e772c 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java @@ -3,7 +3,6 @@ package com.newsblur.activity; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.FragmentActivity; import android.widget.Toast; import com.newsblur.util.FeedUtils; diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java b/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java index 9fbeb71ec..5ecb97ede 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Reading.java @@ -16,8 +16,6 @@ import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager.OnPageChangeListener; import android.util.Log; import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.ProgressBar; diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/SearchForFeeds.java b/clients/android/NewsBlur/src/com/newsblur/activity/SearchForFeeds.java deleted file mode 100644 index b78229573..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/activity/SearchForFeeds.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.newsblur.activity; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashSet; -import java.util.Set; - -import android.app.SearchManager; -import android.content.Intent; -import android.os.Bundle; -import androidx.fragment.app.DialogFragment; -import androidx.loader.app.LoaderManager; -import androidx.loader.app.LoaderManager.LoaderCallbacks; -import androidx.loader.content.Loader; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import com.newsblur.R; -import com.newsblur.domain.FeedResult; -import com.newsblur.fragment.AddFeedFragment; -import com.newsblur.network.SearchAsyncTaskLoader; -import com.newsblur.network.SearchLoaderResponse; -import com.newsblur.util.UIUtils; - -// TODO: this activity's use of the inbuilt activity search facility as well as an improper use of a loader to -// make network requests makes it easily lose state, lack non-legacy progress indication, and generally -// buggy. a normal layout and a proper use of sync for search results should be implemented. -public class SearchForFeeds extends NbActivity implements LoaderCallbacks, OnItemClickListener, AddFeedFragment.AddFeedProgressListener { - - private static final Set SUPPORTED_URL_PROTOCOLS = new HashSet(); - static { - SUPPORTED_URL_PROTOCOLS.add("http"); - SUPPORTED_URL_PROTOCOLS.add("https"); - } - - private ListView resultsList; - private Loader searchLoader; - private FeedSearchResultAdapter adapter; - - @Override - protected void onCreate(Bundle arg0) { - super.onCreate(arg0); - - setContentView(R.layout.activity_feed_search); - UIUtils.setupToolbar(this, R.drawable.logo, getString(R.string.title_feed_search), true); - - TextView emptyView = (TextView) findViewById(R.id.empty_view); - resultsList = (ListView) findViewById(R.id.feed_result_list); - resultsList.setEmptyView(emptyView); - resultsList.setOnItemClickListener(this); - resultsList.setItemsCanFocus(false); - searchLoader = LoaderManager.getInstance(this).initLoader(0, new Bundle(), this); - - onSearchRequested(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.search, menu); - return true; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - String query = intent.getStringExtra(SearchManager.QUERY); - - // test to see if a feed URL was passed rather than a search term - if (tryAddByURL(query)) { return; } - - Bundle bundle = new Bundle(); - bundle.putString(SearchAsyncTaskLoader.SEARCH_TERM, query); - searchLoader = LoaderManager.getInstance(this).restartLoader(0, bundle, this); - - searchLoader.forceLoad(); - } - } - - /** - * See if the text entered in the query field was actually a URL so we can skip the - * search step and just let users who know feed URLs directly subscribe. - */ - private boolean tryAddByURL(String s) { - URL u = null; - try { - u = new URL(s); - } catch (MalformedURLException mue) { - ; // this just signals that the string wasn't a URL, we will return - } - if (u == null) { return false; } - if (! SUPPORTED_URL_PROTOCOLS.contains(u.getProtocol())) { return false; }; - if ((u.getHost() == null) || (u.getHost().trim().isEmpty())) { return false; } - - DialogFragment addFeedFragment = AddFeedFragment.newInstance(s, s); - addFeedFragment.show(getSupportFragmentManager(), "dialog"); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.menu_search) { - onSearchRequested(); - return true; - } else if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public Loader onCreateLoader(int loaderId, Bundle bundle) { - String searchTerm = bundle.getString(SearchAsyncTaskLoader.SEARCH_TERM); - return new SearchAsyncTaskLoader(this, searchTerm); - } - - @Override - public void onLoadFinished(Loader loader, SearchLoaderResponse results) { - if(!results.hasError()) { - adapter = new FeedSearchResultAdapter(this, 0, 0, results.getResults()); - resultsList.setAdapter(adapter); - } else { - String message = results.getErrorMessage() == null ? "Error" : results.getErrorMessage(); - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onLoaderReset(Loader loader) { - - } - - @Override - public void onItemClick(AdapterView arg0, View view, int position, long id) { - FeedResult result = adapter.getItem(position); - DialogFragment addFeedFragment = AddFeedFragment.newInstance(result.url, result.label); - addFeedFragment.show(getSupportFragmentManager(), "dialog"); - } - - @Override - public void addFeedStarted() { - runOnUiThread(new Runnable() { - public void run() { - // TODO: this UI should offer some progress indication, since the add API call can block for several seconds - } - }); - } - -} diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/SearchForFeeds.kt b/clients/android/NewsBlur/src/com/newsblur/activity/SearchForFeeds.kt new file mode 100644 index 000000000..68ef31caa --- /dev/null +++ b/clients/android/NewsBlur/src/com/newsblur/activity/SearchForFeeds.kt @@ -0,0 +1,149 @@ +package com.newsblur.activity + +import android.os.AsyncTask +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import androidx.fragment.app.DialogFragment +import com.newsblur.activity.FeedSearchAdapter.OnFeedSearchResultClickListener +import com.newsblur.databinding.ActivityFeedSearchBinding +import com.newsblur.domain.FeedResult +import com.newsblur.fragment.AddFeedFragment +import com.newsblur.fragment.AddFeedFragment.AddFeedProgressListener +import com.newsblur.network.APIManager +import java.net.MalformedURLException +import java.net.URL +import java.util.* + +class SearchForFeeds : NbActivity(), OnFeedSearchResultClickListener, AddFeedProgressListener { + + private val supportedUrlProtocols: MutableSet = HashSet(2) + + init { + supportedUrlProtocols.add("http") + supportedUrlProtocols.add("https") + } + + private lateinit var adapter: FeedSearchAdapter + private lateinit var binding: ActivityFeedSearchBinding + private lateinit var apiManager: APIManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityFeedSearchBinding.inflate(layoutInflater) + setContentView(binding.root) + setupViews() + setupListeners() + apiManager = APIManager(this) + binding.inputSearchQuery.requestFocus() + } + + override fun onFeedSearchResultClickListener(result: FeedResult) { + showAddFeedDialog(result.url, result.label) + } + + override fun addFeedStarted() { + // loading views handled by AddFeedFragment + } + + private fun setupViews() { + setSupportActionBar(binding.toolbar) + supportActionBar?.setDisplayShowTitleEnabled(false) + supportActionBar?.setDisplayShowHomeEnabled(false) + + adapter = FeedSearchAdapter(this) + binding.feedResultList.adapter = adapter + } + + private fun setupListeners() { + binding.toolbarArrow.setOnClickListener { finish() } + binding.toolbarIcon.setOnClickListener { finish() } + binding.clearText.setOnClickListener { binding.inputSearchQuery.setText("") } + + binding.inputSearchQuery.addTextChangedListener(object : TextWatcher { + + private val handler = Handler(Looper.getMainLooper()) + private var searchQueryRunnable: Runnable? = null + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable) { + searchQueryRunnable?.let { handler.removeCallbacks(it) } + searchQueryRunnable = Runnable { + if (tryAddByURL(s.toString())) { + return@Runnable + } + + syncClearIconVisibility(s) + if (s.isNotEmpty()) searchQuery(s) + else syncSearchResults(arrayOf()) + } + handler.postDelayed(searchQueryRunnable!!, 350) + } + }) + } + + private fun searchQuery(query: Editable) { + object : AsyncTask?>() { + override fun onPreExecute() { + super.onPreExecute() + binding.loadingCircle.visibility = View.VISIBLE + binding.clearText.visibility = View.GONE + } + + override fun doInBackground(vararg params: Void?): Array? { + return apiManager.searchForFeed(query.toString()) + } + + override fun onPostExecute(result: Array?) { + binding.loadingCircle.visibility = View.GONE + binding.clearText.visibility = View.VISIBLE + syncSearchResults(result ?: arrayOf()) + } + + + }.execute() + } + + private fun syncClearIconVisibility(query: Editable) { + binding.clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE + } + + private fun syncSearchResults(results: Array) { + adapter.replaceAll(results) + } + + /** + * See if the text entered in the query field was actually a URL so we can skip the + * search step and just let users who know feed URLs directly subscribe. + */ + private fun tryAddByURL(s: String): Boolean { + var u: URL? = null + try { + u = URL(s) + } catch (mue: MalformedURLException) { + // this just signals that the string wasn't a URL, we will return + } + if (u == null) { + return false + } + if (!supportedUrlProtocols.contains(u.protocol)) { + return false + } + if (u.host == null || u.host.trim().isEmpty()) { + return false + } + showAddFeedDialog(s, s) + return true + } + + private fun showAddFeedDialog(feedUrl: String, feedLabel: String) { + val addFeedFragment: DialogFragment = AddFeedFragment.newInstance(feedUrl, feedLabel) + addFeedFragment.show(supportFragmentManager, "dialog") + } +} \ No newline at end of file diff --git a/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java b/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java index 19b6dd1b9..beaea59f8 100644 --- a/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java +++ b/clients/android/NewsBlur/src/com/newsblur/database/StoryViewAdapter.java @@ -704,18 +704,19 @@ public class StoryViewAdapter extends RecyclerView.Adapter 10f) && // the gesture should not start too close to the left edge and - ((e2.getX()-e1.getX()) > 50f) && // move horizontally to the right and - (Math.abs(e1.getY()-e2.getY()) < 25f) // have minimal vertical travel, so we don't capture scrolling gestures - ) { + float displayWidthPx = UIUtils.getDisplayWidthPx(context); + float edgeWithNavGesturesPaddingPx = UIUtils.dp2px(context, 40); + float rightEdgeNavGesturePaddingPx = displayWidthPx - edgeWithNavGesturesPaddingPx; + if (e1.getX() > edgeWithNavGesturesPaddingPx && // the gesture should not start too close to the left edge and + e2.getX() - e1.getX() > 50f && // move horizontally to the right and + Math.abs(distanceY) < 25f) { // have minimal vertical travel, so we don't capture scrolling gestures vh.gestureL2R = true; vh.gestureDebounce = true; return true; } - if ((e1.getX() > 10f) && // the gesture should not start too close to the left edge and - ((e1.getX()-e2.getX()) > 50f) && // move horizontally to the left and - (Math.abs(e1.getY()-e2.getY()) < 25f) // have minimal vertical travel, so we don't capture scrolling gestures - ) { + if (e1.getX() < rightEdgeNavGesturePaddingPx && // the gesture should not start too close to the right edge and + e1.getX() - e2.getX() > 50f && // move horizontally to the left and + Math.abs(distanceY) < 25f) { // have minimal vertical travel, so we don't capture scrolling gestures vh.gestureR2L = true; vh.gestureDebounce = true; return true; diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java index 06bb87e57..26c7c5c4c 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/ReadingItemFragment.java @@ -28,7 +28,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.webkit.WebView.HitTestResult; -import android.widget.ImageView; import android.widget.TextView; import com.newsblur.R; @@ -52,7 +51,6 @@ import com.newsblur.util.PrefsUtils; import com.newsblur.util.StoryChangesState; import com.newsblur.util.StoryUtils; import com.newsblur.util.UIUtils; -import com.newsblur.view.ReadingScrollView; import java.util.HashMap; import java.util.regex.Matcher; @@ -66,12 +64,10 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI public static final String READING_FONT_CHANGED = "readingFontChanged"; public Story story; private FeedSet fs; - private LayoutInflater inflater; private String feedColor, feedTitle, feedFade, feedBorder, feedIconUrl, faviconText; private Classifier classifier; private BroadcastReceiver textSizeReceiver, readingFontReceiver; private boolean displayFeedDetails; - private View view; private UserDetails user; private DefaultFeedView selectedFeedView; private boolean textViewUnavailable; @@ -163,7 +159,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI getActivity().unregisterReceiver(textSizeReceiver); getActivity().unregisterReceiver(readingFontReceiver); binding.readingWebview.setOnTouchListener(null); - view.setOnTouchListener(null); + binding.getRoot().setOnTouchListener(null); getActivity().getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(null); super.onDestroy(); } @@ -184,8 +180,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - this.inflater = inflater; - view = inflater.inflate(R.layout.fragment_readingitem, null); + View view = inflater.inflate(R.layout.fragment_readingitem, container, false); binding = FragmentReadingitemBinding.bind(view); itemCommentBinding = IncludeReadingItemCommentBinding.bind(binding.getRoot()); @@ -196,7 +191,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI registerForContextMenu(binding.readingWebview); binding.readingWebview.setCustomViewLayout(binding.customViewContainer); - binding.readingWebview.setWebviewWrapperLayout(binding.readingScrollview); + binding.readingWebview.setWebviewWrapperLayout(binding.readingContainer); binding.readingWebview.setBackgroundColor(Color.TRANSPARENT); binding.readingWebview.fragment = this; binding.readingWebview.activity = activity; @@ -206,8 +201,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI updateSaveButton(); setupItemCommentsAndShares(); - ReadingScrollView scrollView = (ReadingScrollView) view.findViewById(R.id.reading_scrollview); - scrollView.registerScrollChangeListener(activity); + binding.readingScrollview.registerScrollChangeListener(activity); return view; } @@ -417,15 +411,10 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI } private void setupItemCommentsAndShares() { - new SetupCommentSectionTask(this, view, inflater, story).execute(); + new SetupCommentSectionTask(this, binding.getRoot(), getLayoutInflater(), story).execute(); } private void setupItemMetadata() { - View feedHeader = view.findViewById(R.id.row_item_feed_header); - View feedHeaderBorder = view.findViewById(R.id.item_feed_border); - TextView itemDate = (TextView) view.findViewById(R.id.reading_item_date); - ImageView feedIcon = (ImageView) view.findViewById(R.id.reading_feed_icon); - if ((feedColor == null) || (feedFade == null) || TextUtils.equals(feedColor, "null") || @@ -440,8 +429,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI Color.parseColor("#" + feedFade), }; GradientDrawable gradient = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, colors); - UIUtils.setViewBackground(feedHeader, gradient); - feedHeaderBorder.setBackgroundColor(Color.parseColor("#" + feedBorder)); + UIUtils.setViewBackground(binding.rowItemFeedHeader, gradient); + binding.itemFeedBorder.setBackgroundColor(Color.parseColor("#" + feedBorder)); if (TextUtils.equals(faviconText, "black")) { binding.readingFeedTitle.setTextColor(UIUtils.getColor(getActivity(), R.color.text)); @@ -453,13 +442,13 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI if (!displayFeedDetails) { binding.readingFeedTitle.setVisibility(View.GONE); - feedIcon.setVisibility(View.GONE); + binding.readingFeedIcon.setVisibility(View.GONE); } else { - FeedUtils.iconLoader.displayImage(feedIconUrl, feedIcon, 0, false); + FeedUtils.iconLoader.displayImage(feedIconUrl, binding.readingFeedIcon, 0, false); binding.readingFeedTitle.setText(feedTitle); } - itemDate.setText(StoryUtils.formatLongDate(getActivity(), story.timestamp)); + binding.readingItemDate.setText(StoryUtils.formatLongDate(getActivity(), story.timestamp)); if (story.tags.length <= 0) { binding.readingItemTags.setVisibility(View.GONE); @@ -510,7 +499,7 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI // 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 // is in full release. - View v = inflater.inflate(R.layout.tag_view, null); + View v = getLayoutInflater().inflate(R.layout.tag_view, null); TextView tagText = (TextView) v.findViewById(R.id.tag_text); tagText.setText(tag); diff --git a/clients/android/NewsBlur/src/com/newsblur/network/SearchAsyncTaskLoader.java b/clients/android/NewsBlur/src/com/newsblur/network/SearchAsyncTaskLoader.java deleted file mode 100644 index bb80c2860..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/network/SearchAsyncTaskLoader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.newsblur.network; - -import java.util.ArrayList; - -import android.content.Context; -import androidx.loader.content.AsyncTaskLoader; - -import com.newsblur.domain.FeedResult; - -public class SearchAsyncTaskLoader extends AsyncTaskLoader { - - public static final String SEARCH_TERM = "searchTerm"; - - private String searchTerm; - private APIManager apiManager; - - public SearchAsyncTaskLoader(Context context, String searchTerm) { - super(context); - this.searchTerm = searchTerm; - apiManager = new APIManager(context); - } - - @Override - public SearchLoaderResponse loadInBackground() { - ArrayList list = new ArrayList(); - FeedResult[] results = apiManager.searchForFeed(searchTerm); - if (results != null) { - for (FeedResult result : results) { - list.add(result); - } - } - SearchLoaderResponse response = new SearchLoaderResponse(list); - return response; - } - -} diff --git a/clients/android/NewsBlur/src/com/newsblur/network/SearchLoaderResponse.java b/clients/android/NewsBlur/src/com/newsblur/network/SearchLoaderResponse.java deleted file mode 100644 index 3e2ede9f2..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/network/SearchLoaderResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.newsblur.network; - -import java.util.ArrayList; - -import com.newsblur.domain.FeedResult; - -public class SearchLoaderResponse extends BaseLoaderResponse { - - private ArrayList results; - - /** - * Use to indicate there was a problem w/ the search - * - * @param errorMessage - */ - public SearchLoaderResponse(String errorMessage) { - super(errorMessage); - results = new ArrayList(0); - } - - public SearchLoaderResponse(ArrayList results) { - this.results = results; - } - - public ArrayList getResults() { - return results; - } - - -} diff --git a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java index b4f464a4b..e4785d984 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java @@ -258,8 +258,8 @@ public class FeedUtils { Set impactedFeeds = dbHelper.setStoryReadState(story, read); NbActivity.updateAllActivities(NbActivity.UPDATE_STORY); - triggerSync(context); NBSyncService.addRecountCandidates(impactedFeeds); + triggerSync(context); } /** @@ -398,23 +398,24 @@ public class FeedUtils { } public static void sendStoryBrief(Story story, Context context) { - if (story == null ) { return; } - Intent intent = new Intent(android.content.Intent.ACTION_SEND); + if (story == null) return; + Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Intent.EXTRA_SUBJECT, UIUtils.fromHtml(story.title).toString()); - intent.putExtra(Intent.EXTRA_TEXT, String.format(context.getResources().getString(R.string.send_brief), new Object[]{UIUtils.fromHtml(story.title), story.permalink})); + intent.putExtra(Intent.EXTRA_SUBJECT, story.title); + intent.putExtra(Intent.EXTRA_TEXT, String.format(context.getResources().getString(R.string.send_brief), story.title, story.permalink)); context.startActivity(Intent.createChooser(intent, "Send using")); } public static void sendStoryFull(Story story, Context context) { - if (story == null ) { return; } - String body = getStoryContent(story.storyHash); - Intent intent = new Intent(android.content.Intent.ACTION_SEND); + if (story == null) return; + String body = getStoryText(story.storyHash); + if (TextUtils.isEmpty(body)) body = getStoryContent(story.storyHash); + Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Intent.EXTRA_SUBJECT, UIUtils.fromHtml(story.title).toString()); - intent.putExtra(Intent.EXTRA_TEXT, String.format(context.getResources().getString(R.string.send_full), new Object[]{story.permalink, UIUtils.fromHtml(story.title), UIUtils.fromHtml(body)})); + intent.putExtra(Intent.EXTRA_SUBJECT, story.title); + intent.putExtra(Intent.EXTRA_TEXT, String.format(context.getResources().getString(R.string.send_full), story.title, story.permalink, UIUtils.fromHtml(body))); context.startActivity(Intent.createChooser(intent, "Send using")); } diff --git a/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java index d4ddf68cc..88d5f4420 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/UIUtils.java @@ -4,7 +4,6 @@ import java.io.File; import java.util.Map; import static android.graphics.Bitmap.Config.ARGB_8888; -import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS; import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL; import static com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP; @@ -182,6 +181,10 @@ public class UIUtils { return ((float) px) / context.getResources().getDisplayMetrics().density; } + public static float getDisplayWidthPx(Context context) { + return context.getResources().getDisplayMetrics().widthPixels; + } + /** * Sets the alpha of a view, totally hiding the view if the alpha is so low * as to be invisible, but also obeying intended visibility. @@ -218,7 +221,7 @@ public class UIUtils { // enabled scrolling app bar only for reading if (activity instanceof Reading) { AppBarLayout.LayoutParams p = (AppBarLayout.LayoutParams) toolbar.getLayoutParams(); - p.setScrollFlags(SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS | SCROLL_FLAG_SNAP); + p.setScrollFlags(SCROLL_FLAG_SCROLL | SCROLL_FLAG_SNAP); toolbar.setLayoutParams(p); } diff --git a/clients/android/NewsBlur/src/com/newsblur/view/SelectOnlyEditText.java b/clients/android/NewsBlur/src/com/newsblur/view/SelectOnlyEditText.java index fd5a7d181..960a5973b 100644 --- a/clients/android/NewsBlur/src/com/newsblur/view/SelectOnlyEditText.java +++ b/clients/android/NewsBlur/src/com/newsblur/view/SelectOnlyEditText.java @@ -8,9 +8,11 @@ import android.view.MenuItem; import android.widget.EditText; import android.widget.Toast; +import androidx.appcompat.widget.AppCompatEditText; + import com.newsblur.R; -public class SelectOnlyEditText extends EditText { +public class SelectOnlyEditText extends AppCompatEditText { private Context context; private boolean forceSelection = false; @@ -31,11 +33,6 @@ public class SelectOnlyEditText extends EditText { this.context = context; } - public SelectOnlyEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - this.context = context; - } - public void disableActionMenu() { this.setCustomSelectionActionModeCallback(new ActionMode.Callback() { public boolean onPrepareActionMode(ActionMode mode, Menu menu) { @@ -61,6 +58,7 @@ public class SelectOnlyEditText extends EditText { @Override protected void onSelectionChanged(int start, int end) { + super.onSelectionChanged(start, end); if (forceSelection && (start == end)) { selectAll(); if (context != null) { diff --git a/clients/android/NewsBlur/src/com/newsblur/widget/WidgetUtils.java b/clients/android/NewsBlur/src/com/newsblur/widget/WidgetUtils.java index d1a1a70ca..95614f45f 100644 --- a/clients/android/NewsBlur/src/com/newsblur/widget/WidgetUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/widget/WidgetUtils.java @@ -8,7 +8,6 @@ import android.content.Context; import android.content.Intent; import android.os.SystemClock; -import com.newsblur.R; import com.newsblur.util.Log; import com.newsblur.util.PrefsUtils;