Converting adapters and retrofit services to kotlin.

This commit is contained in:
Amine 2017-05-28 15:29:59 +02:00
parent 97cee06ebe
commit 9b95404500
22 changed files with 918 additions and 1080 deletions

View File

@ -4,6 +4,10 @@
<uses-permission android:name="android.permission.INTERNET" />
<!-- For firebase only -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".MyApp"
android:allowBackup="true"

View File

@ -104,7 +104,7 @@ class AddSourceActivity : AppCompatActivity() {
if (title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()) {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
} else {
api.createSource(title, url, mSpoutsValue, mTags.text.toString(), "").enqueue(object : Callback<SuccessResponse> {
api.createSource(title, url, mSpoutsValue!!, mTags.text.toString(), "").enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (response.body() != null && response.body().isSuccess) {
finish()

View File

@ -55,7 +55,7 @@ class HomeActivity : AppCompatActivity() {
private val REQUEST_INVITE_BYMAIL = 13232
private var mRecyclerView: RecyclerView? = null
private var api: SelfossApi? = null
private var items: List<Item>? = null
private var items: ArrayList<Item> = ArrayList()
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
private var clickBehavior = false
@ -177,15 +177,15 @@ class HomeActivity : AppCompatActivity() {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
try {
val i = items!![viewHolder.adapterPosition]
val position = items!!.indexOf(i)
val i = items[viewHolder.adapterPosition]
val position = items.indexOf(i)
if (shouldBeCardView) {
(mRecyclerView!!.adapter as ItemCardAdapter).removeItemAtIndex(position)
} else {
(mRecyclerView!!.adapter as ItemListAdapter).removeItemAtIndex(position)
}
tabNew!!.setBadgeCount(items!!.size - 1)
tabNew!!.setBadgeCount(items.size - 1)
} catch (e: IndexOutOfBoundsException) {
FirebaseCrash.logcat(Log.ERROR, "SWIPE ERROR", "Swipe index out of bound")
@ -248,7 +248,7 @@ class HomeActivity : AppCompatActivity() {
api!!.unreadItems.enqueue(object : Callback<List<Item>> {
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) {
if (response.body() != null && response.body().isNotEmpty()) {
items = response.body()
items = response.body() as ArrayList<Item>
} else {
items = ArrayList()
}
@ -268,7 +268,7 @@ class HomeActivity : AppCompatActivity() {
api!!.readItems.enqueue(object : Callback<List<Item>> {
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) {
if (response.body() != null && response.body().isNotEmpty()) {
items = response.body()
items = response.body() as ArrayList<Item>
} else {
items = ArrayList()
}
@ -288,7 +288,7 @@ class HomeActivity : AppCompatActivity() {
api!!.starredItems.enqueue(object : Callback<List<Item>> {
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) {
if (response.body() != null && response.body().isNotEmpty()) {
items = response.body()
items = response.body() as ArrayList<Item>
} else {
items = ArrayList()
}
@ -308,14 +308,14 @@ class HomeActivity : AppCompatActivity() {
val mAdapter: RecyclerView.Adapter<*>
if (shouldBeCardView) {
mAdapter = ItemCardAdapter(this, items, api, mCustomTabActivityHelper, internalBrowser, articleViewer, fullHeightCards)
mAdapter = ItemCardAdapter(this, items, api!!, mCustomTabActivityHelper!!, internalBrowser, articleViewer, fullHeightCards)
} else {
mAdapter = ItemListAdapter(this, items, api, mCustomTabActivityHelper, clickBehavior, internalBrowser, articleViewer)
mAdapter = ItemListAdapter(this, items, api!!, mCustomTabActivityHelper!!, clickBehavior, internalBrowser, articleViewer)
}
mRecyclerView!!.adapter = mAdapter
mAdapter.notifyDataSetChanged()
if (items!!.isEmpty()) Toast.makeText(this@HomeActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show()
if (items.isEmpty()) Toast.makeText(this@HomeActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show()
reloadBadges()
}
@ -356,7 +356,7 @@ class HomeActivity : AppCompatActivity() {
R.id.readAll -> {
if (elementsShown == UNREAD_SHOWN) {
mSwipeRefreshLayout!!.isRefreshing = false
val ids = items!!.map { it.id }
val ids = items.map { it.id }
api!!.readAll(ids).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {

View File

@ -23,6 +23,7 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.checkAndDisplayStoreApk
import apps.amine.bou.readerforselfoss.utils.isUrlValid
import com.google.firebase.analytics.FirebaseAnalytics
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import retrofit2.Call
@ -43,6 +44,8 @@ class LoginActivity : AppCompatActivity() {
private var isWithLogin = false
private var isWithHTTPLogin = false
private var mLoginFormView: View? = null
private var mFirebaseAnalytics: FirebaseAnalytics? = null
override fun onCreate(savedInstanceState: Bundle?) {
@ -60,6 +63,7 @@ class LoginActivity : AppCompatActivity() {
isWithHTTPLogin = false
inValidCount = 0
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this)
mUrlView = findViewById(R.id.url) as EditText
mLoginView = findViewById(R.id.login) as TextView
mHTTPLoginView = findViewById(R.id.httpLogin) as TextView
@ -200,6 +204,7 @@ class LoginActivity : AppCompatActivity() {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (response.body() != null && response.body().isSuccess) {
mFirebaseAnalytics!!.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
goToMain()
} else {
preferenceError()

View File

@ -28,7 +28,7 @@ class SourcesActivity : AppCompatActivity() {
val mRecyclerView = findViewById(R.id.activity_sources) as RecyclerView
val mLayoutManager = LinearLayoutManager(this)
val api = SelfossApi(this)
var items: List<Sources> = ArrayList()
var items: ArrayList<Sources> = ArrayList()
mFab.attachToRecyclerView(mRecyclerView)
mRecyclerView.setHasFixedSize(true)
@ -37,7 +37,7 @@ class SourcesActivity : AppCompatActivity() {
api.sources.enqueue(object : Callback<List<Sources>> {
override fun onResponse(call: Call<List<Sources>>, response: Response<List<Sources>>) {
if (response.body() != null && response.body().isNotEmpty()) {
items = response.body()
items = response.body() as ArrayList<Sources>
}
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
mRecyclerView.adapter = mAdapter

View File

@ -1,310 +0,0 @@
package apps.amine.bou.readerforselfoss.adapters;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.support.constraint.ConstraintLayout;
import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.Snackbar;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
import android.widget.Toast;
import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.api.selfoss.Item;
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi;
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse;
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.TextDrawable.IBuilder;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import com.like.LikeButton;
import com.like.OnLikeListener;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static apps.amine.bou.readerforselfoss.utils.LinksUtilsKt.buildCustomTabsIntent;
import static apps.amine.bou.readerforselfoss.utils.LinksUtilsKt.openItemUrl;
public class ItemCardAdapter extends RecyclerView.Adapter<ItemCardAdapter.ViewHolder> {
private final List<Item> items;
private final SelfossApi api;
private final CustomTabActivityHelper helper;
private final Context c;
private final boolean internalBrowser;
private final boolean articleViewer;
private final Activity app;
private final ColorGenerator generator;
private final boolean fullHeightCards;
public ItemCardAdapter(Activity a, List<Item> myObject, SelfossApi selfossApi,
CustomTabActivityHelper mCustomTabActivityHelper, boolean internalBrowser,
boolean articleViewer, boolean fullHeightCards) {
app = a;
items = myObject;
api = selfossApi;
helper = mCustomTabActivityHelper;
c = app.getApplicationContext();
this.internalBrowser = internalBrowser;
this.articleViewer = articleViewer;
generator = ColorGenerator.MATERIAL;
this.fullHeightCards = fullHeightCards;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ConstraintLayout v = (ConstraintLayout) LayoutInflater.from(c).inflate(R.layout.card_item, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Item itm = items.get(position);
holder.saveBtn.setLiked((itm.getStarred()));
holder.title.setText(Html.fromHtml(itm.getTitle()));
String sourceAndDate = itm.getSourcetitle();
long d;
try {
d = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.getDatetime()).getTime();
sourceAndDate += " " +
DateUtils.getRelativeTimeSpanString(
d,
new Date().getTime(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
);
} catch (ParseException e) {
e.printStackTrace();
}
holder.sourceTitleAndDate.setText(sourceAndDate);
if (itm.getThumbnail(c).isEmpty()) {
Glide.clear(holder.itemImage);
holder.itemImage.setImageDrawable(null);
} else {
if (fullHeightCards) {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().fitCenter().into(holder.itemImage);
} else {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.itemImage);
}
}
final ViewHolder fHolder = holder;
if (itm.getIcon(c).isEmpty()) {
int color = generator.getColor(itm.getSourcetitle());
StringBuilder textDrawable = new StringBuilder();
for(String s : itm.getSourcetitle().split(" "))
{
textDrawable.append(s.charAt(0));
}
IBuilder builder = TextDrawable.builder().round();
TextDrawable drawable = builder.build(textDrawable.toString(), color);
holder.sourceImage.setImageDrawable(drawable);
} else {
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(new BitmapImageViewTarget(holder.sourceImage) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(c.getResources(), resource);
circularBitmapDrawable.setCircular(true);
fHolder.sourceImage.setImageDrawable(circularBitmapDrawable);
}
});
}
holder.saveBtn.setLiked(itm.getStarred());
}
@Override
public int getItemCount() {
return items.size();
}
private void doUnmark(final Item i, final int position) {
Snackbar s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG)
.setAction(R.string.undo_string, new View.OnClickListener() {
@Override
public void onClick(View view) {
items.add(position, i);
notifyItemInserted(position);
api.unmarkItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
items.remove(i);
notifyItemRemoved(position);
doUnmark(i, position);
}
});
}
});
View view = s.getView();
TextView tv = (TextView) view.findViewById(android.support.design.R.id.snackbar_text);
tv.setTextColor(Color.WHITE);
s.show();
}
public void removeItemAtIndex(final int position) {
final Item i = items.get(position);
items.remove(i);
notifyItemRemoved(position);
api.markItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {
doUnmark(i, position);
}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show();
items.add(i);
notifyItemInserted(position);
}
});
}
public class ViewHolder extends RecyclerView.ViewHolder {
LikeButton saveBtn;
ImageButton browserBtn;
ImageButton shareBtn;
ImageView itemImage;
ImageView sourceImage;
TextView title;
TextView sourceTitleAndDate;
public ConstraintLayout mView;
public ViewHolder(ConstraintLayout itemView) {
super(itemView);
mView = itemView;
handleClickListeners();
handleCustomTabActions();
}
private void handleClickListeners() {
sourceImage = (ImageView) mView.findViewById( R.id.sourceImage);
itemImage = (ImageView) mView.findViewById( R.id.itemImage);
title = (TextView) mView.findViewById( R.id.title);
sourceTitleAndDate = (TextView) mView.findViewById( R.id.sourceTitleAndDate);
saveBtn = (LikeButton) mView.findViewById( R.id.favButton);
shareBtn = (ImageButton) mView.findViewById( R.id.shareBtn);
browserBtn = (ImageButton) mView.findViewById( R.id.browserBtn);
if (!fullHeightCards) {
itemImage.setMaxHeight((int) c.getResources().getDimension(R.dimen.card_image_max_height));
itemImage.setScaleType(ScaleType.CENTER_CROP);
}
saveBtn.setOnLikeListener(new OnLikeListener() {
@Override
public void liked(LikeButton likeButton) {
Item i = items.get(getAdapterPosition());
api.starrItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
saveBtn.setLiked(false);
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void unLiked(LikeButton likeButton) {
Item i = items.get(getAdapterPosition());
api.unstarrItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
saveBtn.setLiked(true);
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show();
}
});
}
});
shareBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Item i = items.get(getAdapterPosition());
Intent sendIntent = new Intent();
sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded());
sendIntent.setType("text/plain");
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
});
browserBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Item i = items.get(getAdapterPosition());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse(i.getLinkDecoded()));
c.startActivity(intent);
}
});
}
private void handleCustomTabActions() {
final CustomTabsIntent customTabsIntent = buildCustomTabsIntent(c);
helper.bindCustomTabsService(app);
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openItemUrl(items.get(getAdapterPosition()),
customTabsIntent,
internalBrowser,
articleViewer,
app,
c);
}
});
}
}
}

View File

@ -0,0 +1,252 @@
package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.RecyclerView
import android.text.Html
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.ImageView.ScaleType
import android.widget.TextView
import android.widget.Toast
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.openItemUrl
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
import com.like.LikeButton
import com.like.OnLikeListener
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
class ItemCardAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi,
private val helper: CustomTabActivityHelper, private val internalBrowser: Boolean,
private val articleViewer: Boolean, private val fullHeightCards: Boolean) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.applicationContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as ConstraintLayout
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position]
holder.saveBtn!!.isLiked = itm.starred
holder.title!!.text = Html.fromHtml(itm.title)
var sourceAndDate = itm.sourcetitle
val d: Long
try {
d = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.datetime).time
sourceAndDate += " " + DateUtils.getRelativeTimeSpanString(
d,
Date().time,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
} catch (e: ParseException) {
e.printStackTrace()
}
holder.sourceTitleAndDate!!.text = sourceAndDate
if (itm.getThumbnail(c).isEmpty()) {
Glide.clear(holder.itemImage)
holder.itemImage!!.setImageDrawable(null)
} else {
if (fullHeightCards) {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().fitCenter().into(holder.itemImage)
} else {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.itemImage)
}
}
val fHolder = holder
if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle)
val textDrawable = StringBuilder()
for (s in itm.sourcetitle.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
textDrawable.append(s[0])
}
val builder = TextDrawable.builder().round()
val drawable = builder.build(textDrawable.toString(), color)
holder.sourceImage!!.setImageDrawable(drawable)
} else {
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
override fun setResource(resource: Bitmap) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
circularBitmapDrawable.isCircular = true
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
}
})
}
holder.saveBtn!!.isLiked = itm.starred
}
override fun getItemCount(): Int {
return items.size
}
private fun doUnmark(i: Item, position: Int) {
val s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG)
.setAction(R.string.undo_string) {
items.add(position, i)
notifyItemInserted(position)
api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
items.remove(i)
notifyItemRemoved(position)
doUnmark(i, position)
}
})
}
val view = s.view
val tv = view.findViewById(android.support.design.R.id.snackbar_text) as TextView
tv.setTextColor(Color.WHITE)
s.show()
}
fun removeItemAtIndex(position: Int) {
val i = items[position]
items.remove(i)
notifyItemRemoved(position)
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
doUnmark(i, position)
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show()
items.add(i)
notifyItemInserted(position)
}
})
}
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
var saveBtn: LikeButton? = null
var browserBtn: ImageButton? = null
var shareBtn: ImageButton? = null
var itemImage: ImageView? = null
var sourceImage: ImageView? = null
var title: TextView? = null
var sourceTitleAndDate: TextView? = null
init {
handleClickListeners()
handleCustomTabActions()
}
private fun handleClickListeners() {
sourceImage = mView.findViewById(R.id.sourceImage) as ImageView
itemImage = mView.findViewById(R.id.itemImage) as ImageView
title = mView.findViewById(R.id.title) as TextView
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView
saveBtn = mView.findViewById(R.id.favButton) as LikeButton
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton
if (!fullHeightCards) {
itemImage!!.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
itemImage!!.scaleType = ScaleType.CENTER_CROP
}
saveBtn!!.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn!!.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
}
})
}
override fun unLiked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn!!.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
}
})
}
})
shareBtn!!.setOnClickListener {
val i = items[adapterPosition]
val sendIntent = Intent()
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded())
sendIntent.type = "text/plain"
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
browserBtn!!.setOnClickListener {
val i = items[adapterPosition]
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(i.getLinkDecoded())
c.startActivity(intent)
}
}
private fun handleCustomTabActions() {
val customTabsIntent = buildCustomTabsIntent(c)
helper.bindCustomTabsService(app)
mView.setOnClickListener {
openItemUrl(items[adapterPosition],
customTabsIntent,
internalBrowser,
articleViewer,
app,
c)
}
}
}
}

View File

@ -1,363 +0,0 @@
package apps.amine.bou.readerforselfoss.adapters;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.support.constraint.ConstraintLayout;
import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.Snackbar;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.text.format.DateUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.api.selfoss.Item;
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi;
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse;
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import com.like.LikeButton;
import com.like.OnLikeListener;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static apps.amine.bou.readerforselfoss.utils.LinksUtilsKt.buildCustomTabsIntent;
import static apps.amine.bou.readerforselfoss.utils.LinksUtilsKt.openItemUrl;
public class ItemListAdapter extends RecyclerView.Adapter<ItemListAdapter.ViewHolder> {
private final boolean clickBehavior;
private final boolean articleViewer;
private final boolean internalBrowser;
private final ColorGenerator generator;
private SelfossApi api;
private Context c;
private List<Item> items;
private List<Boolean> bars;
private Activity app;
private CustomTabActivityHelper helper;
public ItemListAdapter(Activity a, List<Item> myObject, SelfossApi selfossApi,
CustomTabActivityHelper mCustomTabActivityHelper, boolean clickBehavior,
boolean internalBrowser, boolean articleViewer) {
app = a;
items = myObject;
api = selfossApi;
helper = mCustomTabActivityHelper;
c = app.getApplicationContext();
this.clickBehavior = clickBehavior;
this.internalBrowser = internalBrowser;
this.articleViewer = articleViewer;
bars = new ArrayList<>(Collections.nCopies(items.size() + 1, false));
generator = ColorGenerator.MATERIAL;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ConstraintLayout v = (ConstraintLayout) LayoutInflater.from(c).inflate(R.layout.list_item, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Item itm = items.get(position);
holder.saveBtn.setLiked((itm.getStarred()));
holder.title.setText(Html.fromHtml(itm.getTitle()));
String sourceAndDate = itm.getSourcetitle();
long d;
try {
d = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.getDatetime()).getTime();
sourceAndDate += " " +
DateUtils.getRelativeTimeSpanString(
d,
new Date().getTime(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
);
} catch (ParseException e) {
e.printStackTrace();
}
holder.sourceTitleAndDate.setText(sourceAndDate);
if (itm.getThumbnail(c).isEmpty()) {
int sizeInInt = 46;
int sizeInDp = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, sizeInInt, c.getResources()
.getDisplayMetrics());
int marginInInt = 16;
int marginInDp = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, marginInInt, c.getResources()
.getDisplayMetrics());
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) holder.sourceImage.getLayoutParams();
params.height = sizeInDp;
params.width = sizeInDp;
params.setMargins(marginInDp, 0, 0, 0);
holder.sourceImage.setLayoutParams(params);
if (itm.getIcon(c).isEmpty()) {
int color = generator.getColor(itm.getSourcetitle());
StringBuilder textDrawable = new StringBuilder();
for(String s : itm.getSourcetitle().split(" "))
{
textDrawable.append(s.charAt(0));
}
TextDrawable.IBuilder builder = TextDrawable.builder().round();
TextDrawable drawable = builder.build(textDrawable.toString(), color);
holder.sourceImage.setImageDrawable(drawable);
} else {
final ViewHolder fHolder = holder;
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(new BitmapImageViewTarget(holder.sourceImage) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(c.getResources(), resource);
circularBitmapDrawable.setCircular(true);
fHolder.sourceImage.setImageDrawable(circularBitmapDrawable);
}
});
}
} else {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.sourceImage);
}
if (bars.get(position)) {
holder.actionBar.setVisibility(View.VISIBLE);
} else {
holder.actionBar.setVisibility(View.GONE);
}
holder.saveBtn.setLiked(itm.getStarred());
}
@Override
public int getItemCount() {
return items.size();
}
private void doUnmark(final Item i, final int position) {
Snackbar s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG)
.setAction(R.string.undo_string, new View.OnClickListener() {
@Override
public void onClick(View view) {
items.add(position, i);
notifyItemInserted(position);
api.unmarkItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
items.remove(i);
notifyItemRemoved(position);
doUnmark(i, position);
}
});
}
});
View view = s.getView();
TextView tv = (TextView) view.findViewById(android.support.design.R.id.snackbar_text);
tv.setTextColor(Color.WHITE);
s.show();
}
public void removeItemAtIndex(final int position) {
final Item i = items.get(position);
items.remove(i);
notifyItemRemoved(position);
api.markItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {
doUnmark(i, position);
}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show();
items.add(i);
notifyItemInserted(position);
}
});
}
public class ViewHolder extends RecyclerView.ViewHolder {
LikeButton saveBtn;
ImageButton browserBtn;
ImageButton shareBtn;
public RelativeLayout actionBar;
ImageView sourceImage;
TextView title;
TextView sourceTitleAndDate;
public ConstraintLayout mView;
public ViewHolder(ConstraintLayout itemView) {
super(itemView);
mView = itemView;
handleClickListeners();
handleCustomTabActions();
}
private void handleClickListeners() {
actionBar = (RelativeLayout) mView.findViewById(R.id.actionBar);
sourceImage = (ImageView) mView.findViewById( R.id.itemImage);
title = (TextView) mView.findViewById( R.id.title);
sourceTitleAndDate = (TextView) mView.findViewById( R.id.sourceTitleAndDate);
saveBtn = (LikeButton) mView.findViewById( R.id.favButton);
shareBtn = (ImageButton) mView.findViewById( R.id.shareBtn);
browserBtn = (ImageButton) mView.findViewById( R.id.browserBtn);
saveBtn.setOnLikeListener(new OnLikeListener() {
@Override
public void liked(LikeButton likeButton) {
Item i = items.get(getAdapterPosition());
api.starrItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
saveBtn.setLiked(false);
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void unLiked(LikeButton likeButton) {
Item i = items.get(getAdapterPosition());
api.unstarrItem(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
saveBtn.setLiked(true);
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show();
}
});
}
});
shareBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Item i = items.get(getAdapterPosition());
Intent sendIntent = new Intent();
sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded());
sendIntent.setType("text/plain");
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
});
browserBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Item i = items.get(getAdapterPosition());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse(i.getLinkDecoded()));
c.startActivity(intent);
}
});
}
private void handleCustomTabActions() {
final CustomTabsIntent customTabsIntent = buildCustomTabsIntent(c);
helper.bindCustomTabsService(app);
if (!clickBehavior) {
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
openItemUrl(items.get(getAdapterPosition()),
customTabsIntent,
internalBrowser,
articleViewer,
app,
c);
}
});
mView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
actionBarShowHide();
return true;
}
});
} else {
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
actionBarShowHide();
}
});
mView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
openItemUrl(items.get(getAdapterPosition()),
customTabsIntent,
internalBrowser,
articleViewer,
app,
c);
return true;
}
});
}
}
private void actionBarShowHide() {
bars.set(getAdapterPosition(), true);
if (actionBar.getVisibility() == View.GONE)
actionBar.setVisibility(View.VISIBLE);
else
actionBar.setVisibility(View.GONE);
}
}
}

View File

@ -0,0 +1,290 @@
package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.RecyclerView
import android.text.Html
import android.text.format.DateUtils
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.openItemUrl
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
import com.like.LikeButton
import com.like.OnLikeListener
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
class ItemListAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi,
private val helper: CustomTabActivityHelper, private val clickBehavior: Boolean,
private val internalBrowser: Boolean, private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.applicationContext
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.list_item, parent, false) as ConstraintLayout
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position]
holder.saveBtn!!.isLiked = itm.starred
holder.title!!.text = Html.fromHtml(itm.title)
var sourceAndDate = itm.sourcetitle
val d: Long
try {
d = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.datetime).time
sourceAndDate += " " + DateUtils.getRelativeTimeSpanString(
d,
Date().time,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
} catch (e: ParseException) {
e.printStackTrace()
}
holder.sourceTitleAndDate!!.text = sourceAndDate
if (itm.getThumbnail(c).isEmpty()) {
val sizeInInt = 46
val sizeInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
.displayMetrics).toInt()
val marginInInt = 16
val marginInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
.displayMetrics).toInt()
val params = holder.sourceImage!!.layoutParams as ViewGroup.MarginLayoutParams
params.height = sizeInDp
params.width = sizeInDp
params.setMargins(marginInDp, 0, 0, 0)
holder.sourceImage!!.layoutParams = params
if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle)
val textDrawable = StringBuilder()
for (s in itm.sourcetitle.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
textDrawable.append(s[0])
}
val builder = TextDrawable.builder().round()
val drawable = builder.build(textDrawable.toString(), color)
holder.sourceImage!!.setImageDrawable(drawable)
} else {
val fHolder = holder
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
override fun setResource(resource: Bitmap) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
circularBitmapDrawable.isCircular = true
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
}
})
}
} else {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.sourceImage)
}
if (bars[position]) {
holder.actionBar!!.visibility = View.VISIBLE
} else {
holder.actionBar!!.visibility = View.GONE
}
holder.saveBtn!!.isLiked = itm.starred
}
override fun getItemCount(): Int {
return items.size
}
private fun doUnmark(i: Item, position: Int) {
val s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG)
.setAction(R.string.undo_string) {
items.add(position, i)
notifyItemInserted(position)
api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
items.remove(i)
notifyItemRemoved(position)
doUnmark(i, position)
}
})
}
val view = s.view
val tv = view.findViewById(android.support.design.R.id.snackbar_text) as TextView
tv.setTextColor(Color.WHITE)
s.show()
}
fun removeItemAtIndex(position: Int) {
val i = items[position]
items.remove(i)
notifyItemRemoved(position)
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
doUnmark(i, position)
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show()
items.add(i)
notifyItemInserted(position)
}
})
}
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
var saveBtn: LikeButton? = null
var browserBtn: ImageButton? = null
var shareBtn: ImageButton? = null
var actionBar: RelativeLayout? = null
var sourceImage: ImageView? = null
var title: TextView? = null
var sourceTitleAndDate: TextView? = null
init {
handleClickListeners()
handleCustomTabActions()
}
private fun handleClickListeners() {
actionBar = mView.findViewById(R.id.actionBar) as RelativeLayout
sourceImage = mView.findViewById(R.id.itemImage) as ImageView
title = mView.findViewById(R.id.title) as TextView
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView
saveBtn = mView.findViewById(R.id.favButton) as LikeButton
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton
saveBtn!!.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn!!.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
}
})
}
override fun unLiked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn!!.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
}
})
}
})
shareBtn!!.setOnClickListener {
val i = items[adapterPosition]
val sendIntent = Intent()
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded())
sendIntent.type = "text/plain"
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
browserBtn!!.setOnClickListener {
val i = items[adapterPosition]
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(i.getLinkDecoded())
c.startActivity(intent)
}
}
private fun handleCustomTabActions() {
val customTabsIntent = buildCustomTabsIntent(c)
helper.bindCustomTabsService(app)
if (!clickBehavior) {
mView.setOnClickListener {
openItemUrl(items[adapterPosition],
customTabsIntent,
internalBrowser,
articleViewer,
app,
c)
}
mView.setOnLongClickListener {
actionBarShowHide()
true
}
} else {
mView.setOnClickListener { actionBarShowHide() }
mView.setOnLongClickListener {
openItemUrl(items[adapterPosition],
customTabsIntent,
internalBrowser,
articleViewer,
app,
c)
true
}
}
}
private fun actionBarShowHide() {
bars[adapterPosition] = true
if (actionBar!!.visibility == View.GONE)
actionBar!!.visibility = View.VISIBLE
else
actionBar!!.visibility = View.GONE
}
}
}

View File

@ -1,133 +0,0 @@
package apps.amine.bou.readerforselfoss.adapters;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.constraint.ConstraintLayout;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi;
import apps.amine.bou.readerforselfoss.api.selfoss.Sources;
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import java.util.List;
public class SourcesListAdapter extends RecyclerView.Adapter<SourcesListAdapter.ViewHolder> {
private final List<Sources> items;
private final Activity app;
private final SelfossApi api;
private final Context c;
private final ColorGenerator generator;
public SourcesListAdapter(Activity activity, List<Sources> items, SelfossApi api) {
this.app = activity;
this.items = items;
this.api = api;
this.c = app.getBaseContext();
generator = ColorGenerator.MATERIAL;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ConstraintLayout v = (ConstraintLayout) LayoutInflater.from(c).inflate(R.layout.source_list_item, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Sources itm = items.get(position);
final ViewHolder fHolder = holder;
if (itm.getIcon(c).isEmpty()) {
int color = generator.getColor(itm.getTitle());
StringBuilder textDrawable = new StringBuilder();
for(String s : itm.getTitle().split(" "))
{
textDrawable.append(s.charAt(0));
}
TextDrawable.IBuilder builder = TextDrawable.builder().round();
TextDrawable drawable = builder.build(textDrawable.toString(), color);
holder.sourceImage.setImageDrawable(drawable);
} else {
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(new BitmapImageViewTarget(holder.sourceImage) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.create(c.getResources(), resource);
circularBitmapDrawable.setCircular(true);
fHolder.sourceImage.setImageDrawable(circularBitmapDrawable);
}
});
}
holder.sourceTitle.setText(itm.getTitle());
}
@Override
public int getItemCount() {
return items.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ConstraintLayout mView;
ImageView sourceImage;
TextView sourceTitle;
Button deleteBtn;
public ViewHolder(ConstraintLayout itemView) {
super(itemView);
mView = itemView;
handleClickListeners();
}
private void handleClickListeners() {
sourceImage = (ImageView) mView.findViewById(R.id.itemImage);
sourceTitle = (TextView) mView.findViewById(R.id.sourceTitle);
deleteBtn = (Button) mView.findViewById(R.id.deleteBtn);
deleteBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Sources i = items.get(getAdapterPosition());
api.deleteSource(i.getId()).enqueue(new Callback<SuccessResponse>() {
@Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {
if (response.body() != null && response.body().isSuccess()) {
items.remove(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
notifyItemRangeChanged(getAdapterPosition(), getItemCount());
}
else {
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<SuccessResponse> call, Throwable t) {
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show();
}
});
}
});
}
}
}

View File

@ -0,0 +1,105 @@
package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.support.constraint.ConstraintLayout
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class SourcesListAdapter(private val app: Activity, private val items: ArrayList<Sources>, private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.source_list_item, parent, false) as ConstraintLayout
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position]
val fHolder = holder
if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.title)
val textDrawable = StringBuilder()
for (s in itm.title.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
textDrawable.append(s[0])
}
val builder = TextDrawable.builder().round()
val drawable = builder.build(textDrawable.toString(), color)
holder.sourceImage!!.setImageDrawable(drawable)
} else {
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
override fun setResource(resource: Bitmap) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
circularBitmapDrawable.isCircular = true
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
}
})
}
holder.sourceTitle!!.text = itm.title
}
override fun getItemCount(): Int {
return items.size
}
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
var sourceImage: ImageView? = null
var sourceTitle: TextView? = null
init {
handleClickListeners()
}
private fun handleClickListeners() {
sourceImage = mView.findViewById(R.id.itemImage) as ImageView
sourceTitle = mView.findViewById(R.id.sourceTitle) as TextView
val deleteBtn = mView.findViewById(R.id.deleteBtn) as Button
deleteBtn.setOnClickListener {
val (id) = items[adapterPosition]
api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (response.body() != null && response.body().isSuccess) {
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount)
} else {
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show()
}
})
}
}
}
}

View File

@ -1,36 +0,0 @@
package apps.amine.bou.readerforselfoss.api.mercury;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MercuryApi {
private final MercuryService service;
private final String key;
public MercuryApi(String key) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
this.key = key;
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://mercury.postlight.com").client(client)
.addConverterFactory(GsonConverterFactory.create(gson)).build();
service = retrofit.create(MercuryService.class);
}
public Call<ParsedContent> parseUrl(String url) {
return service.parseUrl(url, this.key);
}
}

View File

@ -0,0 +1,32 @@
package apps.amine.bou.readerforselfoss.api.mercury
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MercuryApi(private val key: String) {
private val service: MercuryService
init {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
val gson = GsonBuilder()
.setLenient()
.create()
val retrofit = Retrofit.Builder().baseUrl("https://mercury.postlight.com").client(client)
.addConverterFactory(GsonConverterFactory.create(gson)).build()
service = retrofit.create(MercuryService::class.java)
}
fun parseUrl(url: String): Call<ParsedContent> {
return service.parseUrl(url, this.key)
}
}

View File

@ -1,13 +0,0 @@
package apps.amine.bou.readerforselfoss.api.mercury;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Query;
public interface MercuryService {
@GET("parser")
Call<ParsedContent> parseUrl(@Query("url") String url, @Header("x-api-key") String key);
}

View File

@ -0,0 +1,13 @@
package apps.amine.bou.readerforselfoss.api.mercury
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
interface MercuryService {
@GET("parser")
fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent>
}

View File

@ -1,138 +0,0 @@
package apps.amine.bou.readerforselfoss.api.selfoss;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import apps.amine.bou.readerforselfoss.utils.Config;
import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.DispatchingAuthenticator;
import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class SelfossApi {
private final SelfossService service;
private final Config config;
private final String userName;
private final String password;
public SelfossApi(Context c) {
this.config = new Config(c);
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
String httpUserName = config.getHttpUserLogin();
String httpPassword = config.getHttpUserPassword();
Credentials credentials = new Credentials(httpUserName, httpPassword);
final BasicAuthenticator basicAuthenticator = new BasicAuthenticator(credentials);
final DigestAuthenticator digestAuthenticator = new DigestAuthenticator(credentials);
// note that all auth schemes should be registered as lowercase!
DispatchingAuthenticator authenticator = new DispatchingAuthenticator.Builder()
.with("digest", digestAuthenticator)
.with("basic", basicAuthenticator)
.build();
OkHttpClient client = httpBuilder
.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
.addInterceptor(new AuthenticationCacheInterceptor(authCache))
.addInterceptor(interceptor)
.build();
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(boolean.class, new BooleanTypeAdapter());
Gson gson = builder
.setLenient()
.create();
userName = config.getUserLogin();
password = config.getUserPassword();
Retrofit retrofit = new Retrofit.Builder().baseUrl(config.getBaseUrl()).client(client)
.addConverterFactory(GsonConverterFactory.create(gson)).build();
service = retrofit.create(SelfossService.class);
}
public Call<SuccessResponse> login() {
return service.loginToSelfoss(config.getUserLogin(), config.getUserPassword());
}
public Call<List<Item>> getReadItems() {
return getItems("read");
}
public Call<List<Item>> getUnreadItems() {
return getItems("unread");
}
public Call<List<Item>> getStarredItems() {
return getItems("starred");
}
private Call<List<Item>> getItems(String type) {
return service.getItems(type, userName, password);
}
public Call<SuccessResponse> markItem(String itemId) {
return service.markAsRead(itemId, userName, password);
}
public Call<SuccessResponse> unmarkItem(String itemId) {
return service.unmarkAsRead(itemId, userName, password);
}
public Call<SuccessResponse> readAll(List<String> ids) {
return service.markAllAsRead(ids, userName, password);
}
public Call<SuccessResponse> starrItem(String itemId) {
return service.starr(itemId, userName, password);
}
public Call<SuccessResponse> unstarrItem(String itemId) {
return service.unstarr(itemId, userName, password);
}
public Call<Stats> getStats() {
return service.stats(userName, password);
}
public Call<List<Tag>> getTags() {
return service.tags(userName, password);
}
public Call<String> update() {
return service.update(userName, password);
}
public Call<List<Sources>> getSources() { return service.sources(userName, password); }
public Call<SuccessResponse> deleteSource(String id) { return service.deleteSource(id, userName, password);}
public Call<Map<String, Spout>> spouts() { return service.spouts(userName, password); }
public Call<SuccessResponse> createSource(String title, String url, String spout, String tags, String filter) {return service.createSource(title, url, spout, tags, filter, userName, password);}
}

View File

@ -0,0 +1,134 @@
package apps.amine.bou.readerforselfoss.api.selfoss
import android.content.Context
import apps.amine.bou.readerforselfoss.utils.Config
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
import com.burgstaller.okhttp.DispatchingAuthenticator
import com.burgstaller.okhttp.basic.BasicAuthenticator
import com.burgstaller.okhttp.digest.CachingAuthenticator
import com.burgstaller.okhttp.digest.Credentials
import com.burgstaller.okhttp.digest.DigestAuthenticator
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.ConcurrentHashMap
class SelfossApi(c: Context) {
private val service: SelfossService
private val config: Config = Config(c)
private val userName: String
private val password: String
init {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val httpBuilder = OkHttpClient.Builder()
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
val httpUserName = config.httpUserLogin
val httpPassword = config.httpUserPassword
val credentials = Credentials(httpUserName, httpPassword)
val basicAuthenticator = BasicAuthenticator(credentials)
val digestAuthenticator = DigestAuthenticator(credentials)
// note that all auth schemes should be registered as lowercase!
val authenticator = DispatchingAuthenticator.Builder()
.with("digest", digestAuthenticator)
.with("basic", basicAuthenticator)
.build()
val client = httpBuilder
.authenticator(CachingAuthenticatorDecorator(authenticator, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache))
.addInterceptor(interceptor)
.build()
val builder = GsonBuilder()
builder.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
val gson = builder
.setLenient()
.create()
userName = config.userLogin
password = config.userPassword
val retrofit = Retrofit.Builder().baseUrl(config.baseUrl).client(client)
.addConverterFactory(GsonConverterFactory.create(gson)).build()
service = retrofit.create(SelfossService::class.java)
}
fun login(): Call<SuccessResponse> {
return service.loginToSelfoss(config.userLogin, config.userPassword)
}
val readItems: Call<List<Item>>
get() = getItems("read")
val unreadItems: Call<List<Item>>
get() = getItems("unread")
val starredItems: Call<List<Item>>
get() = getItems("starred")
private fun getItems(type: String): Call<List<Item>> {
return service.getItems(type, userName, password)
}
fun markItem(itemId: String): Call<SuccessResponse> {
return service.markAsRead(itemId, userName, password)
}
fun unmarkItem(itemId: String): Call<SuccessResponse> {
return service.unmarkAsRead(itemId, userName, password)
}
fun readAll(ids: List<String>): Call<SuccessResponse> {
return service.markAllAsRead(ids, userName, password)
}
fun starrItem(itemId: String): Call<SuccessResponse> {
return service.starr(itemId, userName, password)
}
fun unstarrItem(itemId: String): Call<SuccessResponse> {
return service.unstarr(itemId, userName, password)
}
val stats: Call<Stats>
get() = service.stats(userName, password)
val tags: Call<List<Tag>>
get() = service.tags(userName, password)
fun update(): Call<String> {
return service.update(userName, password)
}
val sources: Call<List<Sources>>
get() = service.sources(userName, password)
fun deleteSource(id: String): Call<SuccessResponse> {
return service.deleteSource(id, userName, password)
}
fun spouts(): Call<Map<String, Spout>> {
return service.spouts(userName, password)
}
fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> {
return service.createSource(title, url, spout, tags, filter, userName, password)
}
}

View File

@ -68,8 +68,8 @@ data class Item(val id: String,
id = source.readString(),
datetime = source.readString(),
title = source.readString(),
unread = source.readByte() != 0,
starred = source.readByte() != 0,
unread = 0.toByte() != source.readByte(),
starred = 0.toByte() != source.readByte(),
thumbnail = source.readString(),
icon = source.readString(),
link = source.readString(),

View File

@ -1,68 +0,0 @@
package apps.amine.bou.readerforselfoss.api.selfoss;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
interface SelfossService {
@GET("login")
Call<SuccessResponse> loginToSelfoss(@Query("username") String username, @Query("password") String password);
@GET("items")
Call<List<Item>> getItems(@Query("type") String type, @Query("username") String username, @Query("password") String password);
@POST("mark/{id}")
Call<SuccessResponse> markAsRead(@Path("id") String id, @Query("username") String username, @Query("password") String password);
@POST("unmark/{id}")
Call<SuccessResponse> unmarkAsRead(@Path("id") String id, @Query("username") String username, @Query("password") String password);
@FormUrlEncoded
@POST("mark")
Call<SuccessResponse> markAllAsRead(@Field("ids[]") List<String> ids, @Query("username") String username, @Query("password") String password);
@POST("starr/{id}")
Call<SuccessResponse> starr(@Path("id") String id, @Query("username") String username, @Query("password") String password);
@POST("unstarr/{id}")
Call<SuccessResponse> unstarr(@Path("id") String id, @Query("username") String username, @Query("password") String password);
@GET("stats")
Call<Stats> stats(@Query("username") String username, @Query("password") String password);
@GET("tags")
Call<List<Tag>> tags(@Query("username") String username, @Query("password") String password);
@GET("update")
Call<String> update(@Query("username") String username, @Query("password") String password);
@GET("sources/spouts")
Call<Map<String, Spout>> spouts(@Query("username") String username, @Query("password") String password);
@GET("sources/list")
Call<List<Sources>> sources(@Query("username") String username, @Query("password") String password);
@DELETE("source/{id}")
Call<SuccessResponse> deleteSource(@Path("id") String id, @Query("username") String username, @Query("password") String password);
@FormUrlEncoded
@POST("source")
Call<SuccessResponse> createSource(@Field("title") String title, @Field("url") String url, @Field("spout") String spout, @Field("tags") String tags, @Field("filter") String filter, @Query("username") String username, @Query("password") String password);
}

View File

@ -0,0 +1,65 @@
package apps.amine.bou.readerforselfoss.api.selfoss
import retrofit2.Call
import retrofit2.http.DELETE
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
internal interface SelfossService {
@GET("login")
fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@GET("items")
fun getItems(@Query("type") type: String, @Query("username") username: String, @Query("password") password: String): Call<List<Item>>
@POST("mark/{id}")
fun markAsRead(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@POST("unmark/{id}")
fun unmarkAsRead(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@FormUrlEncoded
@POST("mark")
fun markAllAsRead(@Field("ids[]") ids: List<String>, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@POST("starr/{id}")
fun starr(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@POST("unstarr/{id}")
fun unstarr(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@GET("stats")
fun stats(@Query("username") username: String, @Query("password") password: String): Call<Stats>
@GET("tags")
fun tags(@Query("username") username: String, @Query("password") password: String): Call<List<Tag>>
@GET("update")
fun update(@Query("username") username: String, @Query("password") password: String): Call<String>
@GET("sources/spouts")
fun spouts(@Query("username") username: String, @Query("password") password: String): Call<Map<String, Spout>>
@GET("sources/list")
fun sources(@Query("username") username: String, @Query("password") password: String): Call<List<Sources>>
@DELETE("source/{id}")
fun deleteSource(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@FormUrlEncoded
@POST("source")
fun createSource(@Field("title") title: String, @Field("url") url: String, @Field("spout") spout: String, @Field("tags") tags: String, @Field("filter") filter: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
}

View File

@ -1,7 +1,6 @@
package apps.amine.bou.readerforselfoss.utils
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri

View File

@ -9,7 +9,7 @@
<string name="prompt_url">"Url"</string>
<string name="withLoginSwitch">"Login required ?"</string>
<string name="withHttpLoginSwitch">"HTTP Login required ?"</string>
<string name="login_url_problem">"Oups. You may need to add a \"/\" at the end of the url."</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"Username"</string>
<string name="prompt_http_login">"HTTP Username"</string>
<string name="label_share">"Share"</string>