Merge pull request #814 from dosiecki/master

Android: Beta Fixes
This commit is contained in:
Samuel Clay 2015-11-12 14:43:46 -08:00
commit 0b7a9dc817
20 changed files with 335 additions and 66 deletions

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.newsblur"
android:versionCode="108"
android:versionName="4.6.0b2" >
android:versionCode="109"
android:versionName="4.6.0b3" >
<uses-sdk
android:minSdkVersion="14"
@ -22,15 +22,21 @@
android:fullBackupContent="@xml/backupscheme" >
<activity
android:name=".activity.Login"
android:name=".activity.InitActivity"
android:label="@string/newsblur"
android:noHistory="true"
android:windowSoftInputMode="adjustResize">
android:noHistory="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name=".activity.Login"
android:label="@string/newsblur"
android:noHistory="true"
android:windowSoftInputMode="adjustResize">
</activity>
<activity
android:name=".activity.LoginProgress"

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

View file

@ -4,7 +4,7 @@
android:layout_height="match_parent" >
<TextView
android:id="@+id/empty_view_text"
android:id="@+id/reading_empty_view_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom|center_horizontal" >
<ListView
android:id="@+id/choose_folders_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?divider"
android:dividerHeight="2dp" />
</RelativeLayout>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<CheckBox
android:id="@+id/choosefolders_foldername"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingLeft="10dp"
android:paddingBottom="7dp"
android:paddingTop="7dp"
android:layout_marginRight="5dp"
android:ellipsize="end"
android:singleLine="true"
android:textSize="16sp"
android:textStyle="bold"
android:button="@null"
android:drawableRight="?android:attr/listChoiceIndicatorMultiple"
/>
</RelativeLayout>

View file

@ -9,5 +9,8 @@
<item android:id="@+id/menu_unfollow"
android:title="@string/menu_unfollow" />
<item android:id="@+id/menu_choose_folders"
android:title="@string/menu_choose_folders" />
</menu>
</menu>

View file

@ -30,6 +30,8 @@
<string name="description_comment_user">Comment user image</string>
<string name="description_empty_list_image">Empty list placeholder</string>
<string name="description_menu">Menu</string>
<string name="title_choose_folders">Choose Folders for Feed %s</string>
<string name="reading_shared_count">%s shares</string>
<string name="reading_comment_count">%s comments</string>
@ -41,10 +43,11 @@
<string name="saved_stories_row_title">SAVED STORIES</string>
<string name="read_stories_title">Read Stories</string>
<string name="saved_stories_title">Saved Stories</string>
<string name="top_level">TOP LEVEL</string>
<string name="error_saving_classifier">There was an error. Check your internet connection.</string>
<string name="share">\"%1$s\" - %2$s</string>
<string name="send_body">%1$s \n\n\"%2$s\" \n\n%3$s… \n\n--\nShared with NewsBlur.com</string>
<string name="share_comment_hint">Comment (Optional)</string>
<string name="share_newsblur">Share \"%s\" to your Blurblog?</string>
<string name="share_this">Share</string>
@ -64,10 +67,11 @@
<string name="reply_to">Reply to \"%s\"</string>
<string name="alert_dialog_ok">Okay</string>
<string name="alert_dialog_cancel">Cancel</string>
<string name="alert_dialog_done">Done</string>
<string name="alert_dialog_openimage">Open image</string>
<string name="alert_dialog_ok">OKAY</string>
<string name="alert_dialog_cancel">CANCEL</string>
<string name="alert_dialog_done">DONE</string>
<string name="alert_dialog_openimage">OPEN IMAGE</string>
<string name="dialog_folders_save">SAVE FOLDERS</string>
<string name="profile">Profile</string>
<string name="profile_location_icon">Location icon</string>
@ -107,6 +111,7 @@
<string name="menu_mark_feed_as_read">Mark feed as read</string>
<string name="menu_delete_feed">Delete feed</string>
<string name="menu_unfollow">Unfollow user</string>
<string name="menu_choose_folders">Choose folders</string>
<string name="menu_mark_folder_as_read">Mark folder as read</string>
<string name="menu_sharenewsblur">Share this story</string>
<string name="menu_textsize">Adjust text size</string>

View file

@ -0,0 +1,44 @@
package com.newsblur.activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.app.Activity;
import android.view.Window;
import com.newsblur.R;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
/**
* The very first activity we launch. Checks to see if there is a user logged in yet and then
* either loads the Main UI or a Login screen as needed. Also responsible for warming up the
* DB connection used by all other Activities.
*/
public class InitActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// this is the first Activity launched; use it to init the global singletons in FeedUtils
FeedUtils.offerInitContext(this);
// see if a user is already logged in; if so, jump to the Main activity
preferenceCheck();
}
private void preferenceCheck() {
SharedPreferences preferences = getSharedPreferences(PrefConstants.PREFERENCES, Context.MODE_PRIVATE);
if (preferences.getString(PrefConstants.PREF_COOKIE, null) != null) {
Intent mainIntent = new Intent(this, Main.class);
startActivity(mainIntent);
} else {
Intent loginIntent = new Intent(this, Login.class);
startActivity(loginIntent);
}
}
}

View file

@ -1,8 +1,5 @@
package com.newsblur.activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.app.Activity;
import android.app.FragmentManager;
@ -11,9 +8,6 @@ import android.view.Window;
import com.newsblur.R;
import com.newsblur.fragment.LoginRegisterFragment;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
public class Login extends Activity {
@ -21,12 +15,6 @@ public class Login extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// this is the first Activity launched; use it to init the global singletons in FeedUtils
FeedUtils.offerInitContext(this);
// see if a user is already logged in; if so, jump to the Main activity
preferenceCheck();
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_login);
FragmentManager fragmentManager = getFragmentManager();
@ -39,13 +27,4 @@ public class Login extends Activity {
}
}
private void preferenceCheck() {
final SharedPreferences preferences = getSharedPreferences(PrefConstants.PREFERENCES, Context.MODE_PRIVATE);
if (preferences.getString(PrefConstants.PREF_COOKIE, null) != null) {
final Intent mainIntent = new Intent(this, Main.class);
startActivity(mainIntent);
}
}
}

View file

@ -8,6 +8,7 @@ import android.widget.Toast;
import com.newsblur.service.NBSyncService;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
@ -45,6 +46,8 @@ public class NbActivity extends Activity {
super.onCreate(bundle);
FeedUtils.offerInitContext(this);
if (bundle != null) {
uniqueLoginKey = bundle.getString(UNIQUE_LOGIN_KEY);
}

View file

@ -87,6 +87,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
@FindView(R.id.reading_overlay_progress_left) ProgressBar overlayProgressLeft;
@FindView(R.id.reading_overlay_text) Button overlayText;
@FindView(R.id.reading_overlay_send) Button overlaySend;
@FindView(R.id.reading_empty_view_text) View emptyViewText;
ViewPager pager;
@ -270,6 +271,7 @@ public abstract class Reading extends NbActivity implements OnPageChangeListener
(story.storyHash.equals(storyHash)) ) {
// now that the pager is getting the right story, make it visible
pager.setVisibility(View.VISIBLE);
emptyViewText.setVisibility(View.INVISIBLE);
pager.setCurrentItem(stories.getPosition(), false);
this.onPageSelected(stories.getPosition());
storyHash = null;

View file

@ -823,6 +823,16 @@ public class BlurDatabaseHelper {
return result;
}
public List<Folder> getFolders() {
Cursor c = getFoldersCursor(null);
List<Folder> folders = new ArrayList<Folder>(c.getCount());
while (c.moveToNext()) {
folders.add(Folder.fromCursor(c));
}
c.close();
return folders;
}
public Loader<Cursor> getFoldersLoader() {
return new QueryCursorLoader(context) {
protected Cursor createCursor() {return getFoldersCursor(cancellationSignal);}

View file

@ -3,7 +3,6 @@ package com.newsblur.database;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@ -146,7 +145,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
String folderName = activeFolderNames.get(convertGroupPositionToActiveFolderIndex(groupPosition));
TextView folderTitle = ((TextView) v.findViewById(R.id.row_foldername));
folderTitle.setText(folderName.toUpperCase());
folderTitle.setText(folderName);
final String canonicalFolderName = flatFolders.get(folderName).name;
folderTitle.setOnClickListener(new OnClickListener() {
@Override
@ -410,7 +409,7 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
}
// create a sorted list of folder display names
List<String> sortedFolderNames = new ArrayList<String>(flatFolders.keySet());
customSortList(sortedFolderNames);
Collections.sort(sortedFolderNames, Folder.FolderNameComparator);
// figure out which sub-folders are hidden because their parents are closed (flat names)
Set<String> hiddenSubFolders = getSubFoldersRecursive(closedFolders);
Set<String> hiddenSubFoldersFlat = new HashSet<String>(hiddenSubFolders.size());
@ -642,27 +641,6 @@ public class FolderListAdapter extends BaseExpandableListAdapter {
return count;
}
/**
* Custom sorting for folders. Handles the special case to keep the root
* folder on top, and also the expectation that *despite locale*, folders
* starting with an underscore should show up on top.
*/
private void customSortList(List<String> list) {
Collections.sort(list, CustomComparator);
}
private final static Comparator<String> CustomComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
if (TextUtils.equals(s1, s2)) return 0;
if (s1.equals(AppConstants.ROOT_FOLDER)) return -1;
if (s2.equals(AppConstants.ROOT_FOLDER)) return 1;
if (s1.startsWith("_")) return -1;
if (s2.startsWith("_")) return 1;
return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
}
};
public void safeClear(Collection c) {
if (c != null) c.clear();
}

View file

@ -6,6 +6,7 @@ import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import com.newsblur.database.DatabaseConstants;
@ -63,7 +64,11 @@ public class Folder {
builder.append(" - ");
}
builder.append(name);
return builder.toString();
return builder.toString().toUpperCase();
}
public String toString() {
return flatName();
}
public void removeOrphanFeedIds(Collection<String> orphanFeedIds) {
@ -80,4 +85,33 @@ public class Folder {
return name.hashCode();
}
public final static Comparator<String> FolderNameComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return compareFolderNames(s1, s2);
}
};
public final static Comparator<Folder> FolderComparator = new Comparator<Folder>() {
@Override
public int compare(Folder f1, Folder f2) {
return compareFolderNames(f1.name, f2.name);
}
};
/**
* Custom sorting for folders. Handles the special case to keep the root
* folder on top, and also the expectation that *despite locale*, folders
* starting with an underscore should show up on top.
*/
private static int compareFolderNames(String s1, String s2) {
if (TextUtils.equals(s1, s2)) return 0;
if (s1.equals(AppConstants.ROOT_FOLDER)) return -1;
if (s2.equals(AppConstants.ROOT_FOLDER)) return 1;
if (s1.startsWith("_")) return -1;
if (s2.startsWith("_")) return 1;
return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
}
}

View file

@ -0,0 +1,122 @@
package com.newsblur.fragment;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.ArraySet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListAdapter;
import android.widget.ListView;
import butterknife.ButterKnife;
import butterknife.FindView;
import butterknife.OnClick;
import com.newsblur.R;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
import com.newsblur.util.FeedUtils;
public class ChooseFoldersFragment extends DialogFragment {
private Feed feed;
@FindView(R.id.choose_folders_list) ListView listView;
public static ChooseFoldersFragment newInstance(Feed feed) {
ChooseFoldersFragment fragment = new ChooseFoldersFragment();
Bundle args = new Bundle();
args.putSerializable("feed", feed);
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
feed = (Feed) getArguments().getSerializable("feed");
final List<Folder> folders = FeedUtils.dbHelper.getFolders();
Collections.sort(folders, Folder.FolderComparator);
final Set<String> newFolders = new ArraySet<String>();
final Set<String> oldFolders = new ArraySet<String>();
for (Folder folder : folders) {
if (folder.feedIds.contains(feed.feedId)) {
newFolders.add(folder.name);
oldFolders.add(folder.name);
}
}
final Activity activity = getActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
View v = inflater.inflate(R.layout.dialog_choosefolders, null);
ButterKnife.bind(this, v);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(String.format(getResources().getString(R.string.title_choose_folders), feed.title));
builder.setView(v);
builder.setNegativeButton(R.string.alert_dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
ChooseFoldersFragment.this.dismiss();
}
});
builder.setPositiveButton(R.string.dialog_folders_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.moveFeedToFolders(activity, feed.feedId, newFolders, oldFolders);
ChooseFoldersFragment.this.dismiss();
}
});
ListAdapter adapter = new ArrayAdapter<Folder>(getActivity(), R.layout.row_choosefolders, R.id.choosefolders_foldername, folders) {
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = super.getView(position, convertView, parent);
CheckBox row = (CheckBox) v.findViewById(R.id.choosefolders_foldername);
if (position == 0) {
row.setText(R.string.top_level);
}
row.setChecked(folders.get(position).feedIds.contains(feed.feedId));
row.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
CheckBox row = (CheckBox) v;
if (row.isChecked()) {
folders.get(position).feedIds.add(feed.feedId);
newFolders.add(folders.get(position).name);
} else {
folders.get(position).feedIds.remove(feed.feedId);
newFolders.remove(folders.get(position).name);
}
}
});
return v;
}
};
listView.setAdapter(adapter);
Dialog dialog = builder.create();
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_DITHER, WindowManager.LayoutParams.FLAG_DITHER);
dialog.getWindow().getAttributes().gravity = Gravity.BOTTOM;
return dialog;
}
}

View file

@ -218,6 +218,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
inflater.inflate(R.menu.context_feed, menu);
if (groupPosition == FolderListAdapter.ALL_SHARED_STORIES_GROUP_POSITION) {
menu.removeItem(R.id.menu_delete_feed);
menu.removeItem(R.id.menu_choose_folders);
} else {
menu.removeItem(R.id.menu_unfollow);
}
@ -258,7 +259,10 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
FeedUtils.markFeedsRead(FeedSet.allFeeds(), null, null, getActivity());
}
return true;
}
} else if (item.getItemId() == R.id.menu_choose_folders) {
DialogFragment chooseFoldersFragment = ChooseFoldersFragment.newInstance(adapter.getFeed(adapter.getChild(groupPosition, childPosition)));
chooseFoldersFragment.show(getFragmentManager(), "dialog");
}
return super.onContextItemSelected(item);
}

View file

@ -27,7 +27,6 @@ public class APIConstants {
public static final String URL_SOCIALFEED_STORIES = NEWSBLUR_URL + "/social/stories";
public static final String URL_SIGNUP = NEWSBLUR_URL + "/api/signup";
public static final String URL_MARK_FEED_AS_READ = NEWSBLUR_URL + "/reader/mark_feed_as_read/";
//public static final String URL_MARK_FEED_AS_READ = "http://httpstat.us/502";
public static final String URL_MARK_ALL_AS_READ = NEWSBLUR_URL + "/reader/mark_all_as_read/";
public static final String URL_MARK_STORIES_READ = NEWSBLUR_URL + "/reader/mark_story_hashes_as_read/";
public static final String URL_SHARE_STORY = NEWSBLUR_URL + "/social/share_story";
@ -46,6 +45,7 @@ public class APIConstants {
public static final String URL_STORY_TEXT = NEWSBLUR_URL + "/rss_feeds/original_text";
public static final String URL_UNREAD_HASHES = NEWSBLUR_URL + "/reader/unread_story_hashes";
public static final String URL_READ_STORIES = NEWSBLUR_URL + "/reader/read_stories";
public static final String URL_MOVE_FEED_TO_FOLDERS = NEWSBLUR_URL + "/reader/move_feed_to_folders";
public static final String PARAMETER_FEEDS = "f";
public static final String PARAMETER_H = "h";
@ -80,6 +80,8 @@ public class APIConstants {
public static final String PARAMETER_GLOBAL_FEED = "global_feed";
public static final String PARAMETER_INCLUDE_HIDDEN = "include_hidden";
public static final String PARAMETER_LIMIT = "limit";
public static final String PARAMETER_TO_FOLDER = "to_folders";
public static final String PARAMETER_IN_FOLDERS = "in_folders";
public static final String VALUE_PREFIX_SOCIAL = "social:";
public static final String VALUE_ALLSOCIAL = "river:blurblogs"; // the magic value passed to the mark-read API for all social feeds

View file

@ -241,6 +241,21 @@ public class APIManager {
}
}
public NewsBlurResponse moveFeedToFolders(String feedId, Set<String> toFolders, Set<String> inFolders) {
ValueMultimap values = new ValueMultimap();
for (String folder : toFolders) {
if (folder.equals(AppConstants.ROOT_FOLDER)) folder = "";
values.put(APIConstants.PARAMETER_TO_FOLDER, folder);
}
for (String folder : inFolders) {
if (folder.equals(AppConstants.ROOT_FOLDER)) folder = "";
values.put(APIConstants.PARAMETER_IN_FOLDERS, folder);
}
values.put(APIConstants.PARAMETER_FEEDID, feedId);
APIResponse response = post(APIConstants.URL_MOVE_FEED_TO_FOLDERS, values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public UnreadCountResponse getFeedUnreadCounts(Set<String> apiIds) {
ValueMultimap values = new ValueMultimap();
for (String id : apiIds) {

View file

@ -32,8 +32,12 @@ public class FeedUtils {
public static ImageLoader imageLoader;
public static void offerInitContext(Context context) {
dbHelper = new BlurDatabaseHelper(context);
imageLoader = new ImageLoader(context);
if (dbHelper == null) {
dbHelper = new BlurDatabaseHelper(context.getApplicationContext());
}
if (imageLoader == null) {
imageLoader = new ImageLoader(context.getApplicationContext());
}
}
private static void triggerSync(Context c) {
@ -206,9 +210,8 @@ public class FeedUtils {
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intent.putExtra(Intent.EXTRA_SUBJECT, Html.fromHtml(story.title));
final String shareString = context.getResources().getString(R.string.share);
intent.putExtra(Intent.EXTRA_TEXT, String.format(shareString, new Object[]{Html.fromHtml(story.title), story.permalink}));
intent.putExtra(Intent.EXTRA_SUBJECT, Html.fromHtml(story.title).toString());
intent.putExtra(Intent.EXTRA_TEXT, String.format(context.getResources().getString(R.string.send_body), new Object[]{story.permalink, Html.fromHtml(story.title), Html.fromHtml(story.shortContent)}));
context.startActivity(Intent.createChooser(intent, "Send using"));
}
@ -247,6 +250,22 @@ public class FeedUtils {
triggerSync(context);
}
public static void moveFeedToFolders(final Context context, final String feedId, final Set<String> toFolders, final Set<String> inFolders) {
if (toFolders.size() < 1) return;
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... arg) {
APIManager apiManager = new APIManager(context);
return apiManager.moveFeedToFolders(feedId, toFolders, inFolders);
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
NBSyncService.forceFeedsFolders();
triggerSync(context);
}
}.execute();
}
public static FeedSet feedSetFromFolderName(String folderName) {
return FeedSet.folder(folderName, getFeedIdsRecursive(folderName));
}

View file

@ -78,7 +78,7 @@ public class PrefsUtils {
StringBuilder s = new StringBuilder(AppConstants.FEEDBACK_URL);
s.append("<give us some feedback!>%0A%0A");
s.append("%0Aapp version: ").append(getVersion(context));
s.append("%0Aandroid version: ").append(Build.VERSION.RELEASE);
s.append("%0Aandroid version: ").append(Build.VERSION.RELEASE).append(" (" + Build.DISPLAY + ")");
s.append("%0Adevice: ").append(Build.MANUFACTURER + "+" + Build.MODEL + "+(" + Build.BOARD + ")");
s.append("%0Asqlite version: ").append(FeedUtils.dbHelper.getEngineVersion());
s.append("%0Ausername: ").append(getUserDetails(context).username);