added error message when no dm permission

This commit is contained in:
Mariotaku Lee 2016-01-31 21:34:49 +08:00
parent b1086d5497
commit b4f88594df
37 changed files with 344 additions and 156 deletions

Binary file not shown.

View File

@ -193,5 +193,6 @@ public class Activity extends TwitterResponseObject implements TwitterResponse,
String FAVORITED_MEDIA_TAGGED = ("favorited_media_tagged");
String RETWEETED_MEDIA_TAGGED = ("retweeted_media_tagged");
String[] MENTION_ACTIONS = {MENTION, REPLY, QUOTE};
}
}

View File

@ -28,6 +28,12 @@ import com.bluelinelabs.logansquare.annotation.JsonObject;
@JsonObject
public class ErrorInfo {
public static final int PAGE_NOT_FOUND = 34;
public static final int RATE_LIMIT_EXCEEDED = 88;
public static final int NOT_AUTHORIZED = 179;
public static final int STATUS_IS_DUPLICATE = 187;
public static final int NO_DIRECT_MESSAGE_PERMISSION = 93;
@JsonField(name = "code")
int code;
@JsonField(name = "message")

View File

@ -209,6 +209,7 @@ public interface IntentConstants {
String EXTRA_CURRENT_MEDIA = "current_media";
String EXTRA_EXTRAS = "extras";
String EXTRA_MY_FOLLOWING_ONLY = "my_following_only";
String EXTRA_MENTIONS_ONLY = "mentions_only";
String EXTRA_CHANGED = "changed";
String EXTRA_NOTIFY_CHANGE = "notify_change";
String EXTRA_RESTART_ACTIVITY = "restart_activity";

View File

@ -265,6 +265,7 @@ public interface SharedPreferenceConstants {
String KEY_NOTIFICATION_TYPE_MENTIONS = "notification_type_mentions";
String KEY_NOTIFICATION_TYPE_DIRECT_MESSAGES = "notification_type_direct_messages";
String KEY_NOTIFICATION_FOLLOWING_ONLY = "notification_following_only";
String KEY_NOTIFICATION_MENTIONS_ONLY = "notification_mentions_only";
@Preference(type = BOOLEAN, hasDefault = true, defaultBoolean = false)
String KEY_PEBBLE_NOTIFICATIONS = "pebble_notifications";

View File

@ -893,7 +893,6 @@ public interface TwidereDataStore {
String ACCOUNT_ID = "account_id";
String ACTION = "action";
String RAW_ACTION = "raw_action";
String TIMESTAMP = "timestamp";
String STATUS_ID = "status_id";
String STATUS_RETWEET_ID = "status_retweet_id";
@ -925,7 +924,7 @@ public interface TwidereDataStore {
IS_GAP, MIN_POSITION, MAX_POSITION, SOURCES, SOURCE_IDS, TARGET_STATUSES, TARGET_USERS,
TARGET_USER_LISTS, TARGET_OBJECT_STATUSES, TARGET_OBJECT_USER_LISTS, TARGET_OBJECT_USERS,
STATUS_RETWEET_ID, STATUS_USER_FOLLOWING, INSERTED_DATE};
String[] TYPES = {TYPE_PRIMARY_KEY, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT,
String[] TYPES = {TYPE_PRIMARY_KEY, TYPE_INT, TYPE_TEXT, TYPE_INT, TYPE_INT, TYPE_INT,
TYPE_INT, TYPE_INT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT,
TYPE_BOOLEAN, TYPE_INT, TYPE_INT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_TEXT,
TYPE_TEXT, TYPE_TEXT, TYPE_TEXT, TYPE_INT, TYPE_BOOLEAN, INSERTED_DATE_TYPE};

View File

@ -87,27 +87,13 @@ public final class TwidereArrayUtils {
}
@SuppressWarnings("SuspiciousSystemArraycopy")
public static void mergeArray(final Object dest, final Object... arrays) {
if (arrays == null || arrays.length == 0) return;
if (arrays.length == 1) {
final Object array = arrays[0];
System.arraycopy(array, 0, dest, 0, Array.getLength(array));
return;
public static void mergeArray(final Object dest, @NonNull final Object... arrays) {
for (int i = 0, j = arrays.length, k = 0; i < j; i++) {
final Object array = arrays[i];
final int length = Array.getLength(array);
System.arraycopy(array, 0, dest, k, length);
k += length;
}
for (int i = 0, j = arrays.length - 1; i < j; i++) {
final Object array1 = arrays[i], array2 = arrays[i + 1];
System.arraycopy(array1, 0, dest, 0, Array.getLength(array1));
System.arraycopy(array2, 0, dest, Array.getLength(array1), Array.getLength(array2));
}
}
public static String mergeArrayToString(final String[] array) {
if (array == null) return null;
final StringBuilder builder = new StringBuilder();
for (final String c : array) {
builder.append(c);
}
return builder.toString();
}
public static long min(final long[] array) {

View File

@ -0,0 +1,14 @@
package org.mariotaku.twidere.util;
import org.junit.Test;
/**
* Created by mariotaku on 16/1/31.
*/
public class TwidereArrayUtilsTest {
@Test
public void testMergeArray() throws Exception {
}
}

View File

@ -67,12 +67,14 @@ import com.squareup.otto.Subscribe;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.SettingsActivity;
import org.mariotaku.twidere.activity.SettingsWizardActivity;
import org.mariotaku.twidere.activity.UsageStatisticsActivity;
import org.mariotaku.twidere.adapter.support.SupportTabsAdapter;
import org.mariotaku.twidere.annotation.CustomTabType;
import org.mariotaku.twidere.api.twitter.model.Activity;
import org.mariotaku.twidere.fragment.CustomTabsFragment;
import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface;
import org.mariotaku.twidere.fragment.iface.SupportFragmentCallback;
@ -955,8 +957,20 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
final String tagWithAccounts = Utils.getReadPositionTagWithAccounts(mContext,
true, spec.tag, accountIds);
final long position = mReadStateManager.getPosition(tagWithAccounts);
Expression extraWhere = null;
String[] extraWhereArgs = null;
if (spec.args != null) {
Bundle extras = spec.args.getBundle(EXTRA_EXTRAS);
if (extras != null && extras.getBoolean(EXTRA_MENTIONS_ONLY)) {
extraWhere = Expression.inArgs(Activities.ACTION, 3);
extraWhereArgs = new String[]{Activity.Action.MENTION,
Activity.Action.REPLY, Activity.Action.QUOTE};
}
}
result.put(spec.position, DataStoreUtils.getActivitiesCount(mContext,
Activities.AboutMe.CONTENT_URI, position, accountIds));
Activities.AboutMe.CONTENT_URI, extraWhere, extraWhereArgs,
position, accountIds));
break;
}
case CustomTabType.DIRECT_MESSAGES: {

View File

@ -116,7 +116,7 @@ public final class MediaViewerActivity extends AbsMediaViewerActivity implements
@Override
public void setBarVisibility(boolean visible) {
ActionBar actionBar = getSupportActionBar();
final ActionBar actionBar = getSupportActionBar();
if (actionBar == null) return;
if (visible) {
actionBar.show();

View File

@ -79,6 +79,7 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
long[] mFilteredUserIds;
boolean mFollowingOnly;
boolean mMentionsOnly;
protected AbsActivitiesAdapter(final Context context, boolean compact) {
@ -245,35 +246,44 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
return ITEM_VIEW_TYPE_GAP;
}
final String action = getActivityAction(position);
if (Activity.Action.MENTION.equals(action)) {
if (ArrayUtils.isEmpty(activity.target_object_statuses)) {
return ITEM_VIEW_TYPE_STUB;
switch (action) {
case Activity.Action.MENTION: {
if (ArrayUtils.isEmpty(activity.target_object_statuses)) {
return ITEM_VIEW_TYPE_STUB;
}
if (mFollowingOnly && !activity.status_user_following) return ITEM_VIEW_TYPE_EMPTY;
return ITEM_VIEW_TYPE_STATUS;
}
if (mFollowingOnly && !activity.status_user_following) return ITEM_VIEW_TYPE_EMPTY;
return ITEM_VIEW_TYPE_STATUS;
} else if (Activity.Action.REPLY.equals(action)) {
if (ArrayUtils.isEmpty(activity.target_statuses)) {
return ITEM_VIEW_TYPE_STUB;
case Activity.Action.REPLY: {
if (ArrayUtils.isEmpty(activity.target_statuses)) {
return ITEM_VIEW_TYPE_STUB;
}
if (mFollowingOnly && !activity.status_user_following) return ITEM_VIEW_TYPE_EMPTY;
return ITEM_VIEW_TYPE_STATUS;
}
if (mFollowingOnly && !activity.status_user_following) return ITEM_VIEW_TYPE_EMPTY;
return ITEM_VIEW_TYPE_STATUS;
} else if (Activity.Action.QUOTE.equals(action)) {
if (ArrayUtils.isEmpty(activity.target_statuses)) {
return ITEM_VIEW_TYPE_STUB;
case Activity.Action.QUOTE: {
if (ArrayUtils.isEmpty(activity.target_statuses)) {
return ITEM_VIEW_TYPE_STUB;
}
if (mFollowingOnly && !activity.status_user_following) return ITEM_VIEW_TYPE_EMPTY;
return ITEM_VIEW_TYPE_STATUS;
}
if (mFollowingOnly && !activity.status_user_following) return ITEM_VIEW_TYPE_EMPTY;
return ITEM_VIEW_TYPE_STATUS;
} else if (Activity.Action.FOLLOW.equals(action) || Activity.Action.FAVORITE.equals(action)
|| Activity.Action.RETWEET.equals(action) || Activity.Action.FAVORITED_RETWEET.equals(action)
|| Activity.Action.RETWEETED_RETWEET.equals(action) || Activity.Action.RETWEETED_MENTION.equals(action)
|| Activity.Action.FAVORITED_MENTION.equals(action) || Activity.Action.LIST_CREATED.equals(action)
|| Activity.Action.LIST_MEMBER_ADDED.equals(action)) {
ParcelableActivityUtils.getAfterFilteredSourceIds(activity, mFilteredUserIds,
mFollowingOnly);
if (ArrayUtils.isEmpty(activity.after_filtered_source_ids)) {
return ITEM_VIEW_TYPE_EMPTY;
case Activity.Action.FOLLOW:
case Activity.Action.FAVORITE:
case Activity.Action.RETWEET:
case Activity.Action.FAVORITED_RETWEET:
case Activity.Action.RETWEETED_RETWEET:
case Activity.Action.RETWEETED_MENTION:
case Activity.Action.FAVORITED_MENTION:
case Activity.Action.LIST_CREATED:
case Activity.Action.LIST_MEMBER_ADDED: {
if (mMentionsOnly) return ITEM_VIEW_TYPE_EMPTY;
ParcelableActivityUtils.initAfterFilteredSourceIds(activity, mFilteredUserIds, mFollowingOnly);
if (ArrayUtils.isEmpty(activity.after_filtered_source_ids)) {
return ITEM_VIEW_TYPE_EMPTY;
}
return ITEM_VIEW_TYPE_TITLE_SUMMARY;
}
return ITEM_VIEW_TYPE_TITLE_SUMMARY;
}
return ITEM_VIEW_TYPE_STUB;
}
@ -283,6 +293,11 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
notifyDataSetChanged();
}
public void setMentionsOnly(boolean mentionsOnly) {
mMentionsOnly = mentionsOnly;
notifyDataSetChanged();
}
@Override
public final int getItemCount() {
return getActivityCount() + (isLoadMoreIndicatorVisible() ? 1 : 0);

View File

@ -30,7 +30,6 @@ import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import android.view.KeyEvent;
@ -117,7 +116,7 @@ public abstract class AbsActivitiesFragment<Data> extends AbsContentListRecycler
}
}
};
private PopupMenu mPopupMenu;
private final OnScrollListener mOnScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
@ -427,9 +426,6 @@ public abstract class AbsActivitiesFragment<Data> extends AbsContentListRecycler
@Override
public void onDestroyView() {
if (mPopupMenu != null) {
mPopupMenu.dismiss();
}
super.onDestroyView();
}

View File

@ -51,8 +51,9 @@ import org.mariotaku.twidere.view.themed.AccentSwipeRefreshLayout;
/**
* Created by mariotaku on 15/10/26.
*/
public abstract class AbsContentRecyclerViewFragment<A extends LoadMoreSupportAdapter, L extends RecyclerView.LayoutManager> extends BaseSupportFragment
implements SwipeRefreshLayout.OnRefreshListener, HeaderDrawerLayout.DrawerCallback, RefreshScrollTopInterface, IControlBarActivity.ControlBarOffsetListener,
public abstract class AbsContentRecyclerViewFragment<A extends LoadMoreSupportAdapter, L extends RecyclerView.LayoutManager>
extends BaseSupportFragment implements SwipeRefreshLayout.OnRefreshListener,
HeaderDrawerLayout.DrawerCallback, RefreshScrollTopInterface, IControlBarActivity.ControlBarOffsetListener,
ContentListScrollListener.ContentListSupport, IControlBarActivity.ControlBarShowHideHelper.ControlBarAnimationListener {
private View mProgressContainer;

View File

@ -25,8 +25,11 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter;
import org.mariotaku.twidere.annotation.ReadPositionTag;
import org.mariotaku.twidere.api.twitter.model.Activity;
import org.mariotaku.twidere.provider.TwidereDataStore.Activities;
import edu.tsinghua.hotmobi.model.TimelineType;
@ -66,6 +69,21 @@ public class ActivitiesAboutMeFragment extends CursorActivitiesFragment {
setRefreshing(mTwitterWrapper.isMentionsTimelineRefreshing());
}
@Override
@NonNull
protected Where processWhere(@NonNull Expression where, @NonNull String[] whereArgs) {
final Bundle arguments = getArguments();
if (arguments != null) {
final Bundle extras = arguments.getBundle(EXTRA_EXTRAS);
if (extras != null && extras.getBoolean(EXTRA_MENTIONS_ONLY)) {
final Expression expression = Expression.and(where, Expression.inArgs(Activities.ACTION, 3));
return new Where(expression, ArrayUtils.addAll(whereArgs, Activity.Action.MENTION,
Activity.Action.REPLY, Activity.Action.QUOTE));
}
}
return super.processWhere(where, whereArgs);
}
@NonNull
@Override
protected ParcelableActivitiesAdapter onCreateAdapter(Context context, boolean compact) {
@ -75,6 +93,7 @@ public class ActivitiesAboutMeFragment extends CursorActivitiesFragment {
final Bundle extras = arguments.getBundle(EXTRA_EXTRAS);
if (extras != null) {
adapter.setFollowingOnly(extras.getBoolean(EXTRA_MY_FOLLOWING_ONLY));
adapter.setMentionsOnly(extras.getBoolean(EXTRA_MENTIONS_ONLY));
}
}
return adapter;

View File

@ -46,6 +46,7 @@ import org.mariotaku.twidere.fragment.iface.IBaseFragment;
import org.mariotaku.twidere.util.AsyncTaskManager;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.DebugModeUtils;
import org.mariotaku.twidere.util.ErrorInfoStore;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MultiSelectManager;
import org.mariotaku.twidere.util.NotificationManagerWrapper;
@ -80,6 +81,8 @@ public class BaseSupportFragment extends Fragment implements IBaseFragment, Cons
protected NotificationManagerWrapper mNotificationManager;
@Inject
protected BidiFormatter mBidiFormatter;
@Inject
protected ErrorInfoStore mErrorInfoStore;
public BaseSupportFragment() {

View File

@ -27,6 +27,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.Loader;
@ -34,6 +35,7 @@ import com.desmond.asyncmanager.AsyncManager;
import com.desmond.asyncmanager.TaskRunnable;
import com.squareup.otto.Subscribe;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.library.objectcursor.ObjectCursor;
import org.mariotaku.sqliteqb.library.Columns.Column;
import org.mariotaku.sqliteqb.library.Expression;
@ -92,19 +94,21 @@ public abstract class CursorActivitiesFragment extends AbsActivitiesFragment<Lis
final String table = getTableNameByUri(uri);
final String sortOrder = getSortOrder();
final long[] accountIds = getAccountIds();
final Expression accountWhere = Expression.in(new Column(Activities.ACCOUNT_ID), new RawItemArray(accountIds));
final Expression accountWhere = Expression.in(new Column(Activities.ACCOUNT_ID),
new RawItemArray(accountIds));
final Expression filterWhere = getFiltersWhere(table), where;
if (filterWhere != null) {
where = Expression.and(accountWhere, filterWhere);
} else {
where = accountWhere;
}
final String selection = processWhere(where).getSQL();
final Where expression = processWhere(where, new String[0]);
final String selection = expression.getSQL();
final AbsActivitiesAdapter<List<ParcelableActivity>> adapter = getAdapter();
adapter.setShowAccountsColor(accountIds.length > 1);
final String[] projection = Activities.COLUMNS;
return new CursorActivitiesLoader(context, uri, projection, selection, null, sortOrder,
fromUser);
return new CursorActivitiesLoader(context, uri, projection, selection, expression.whereArgs,
sortOrder, fromUser);
}
@Override
@ -242,8 +246,9 @@ public abstract class CursorActivitiesFragment extends AbsActivitiesFragment<Lis
protected abstract boolean isFilterEnabled();
protected Expression processWhere(final Expression where) {
return where;
@NonNull
protected Where processWhere(@NonNull final Expression where, @NonNull final String[] whereArgs) {
return new Where(where, whereArgs);
}
protected abstract void updateRefreshState();
@ -293,6 +298,20 @@ public abstract class CursorActivitiesFragment extends AbsActivitiesFragment<Lis
}
public static class Where {
Expression where;
String[] whereArgs;
public Where(@NonNull Expression where, @Nullable String[] whereArgs) {
this.where = where;
this.whereArgs = whereArgs;
}
public String getSQL() {
return where.getSQL();
}
}
public static class CursorActivitiesLoader extends ExtendedObjectCursorLoader<ParcelableActivity> {
public CursorActivitiesLoader(Context context, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,

View File

@ -57,6 +57,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.DataStoreUtils;
import org.mariotaku.twidere.util.ErrorInfoStore;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
import org.mariotaku.twidere.util.RecyclerViewNavigationHelper;
@ -150,17 +151,25 @@ public class DirectMessagesFragment extends AbsContentListRecyclerViewFragment<M
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) {
if (getActivity() == null) return;
final boolean isEmpty = cursor != null && cursor.getCount() == 0;
mFirstVisibleItem = -1;
final MessageEntriesAdapter adapter = getAdapter();
adapter.setCursor(cursor);
adapter.setLoadMoreIndicatorVisible(false);
adapter.setLoadMoreSupported(cursor != null && cursor.getCount() > 0);
adapter.setLoadMoreSupported(!isEmpty);
adapter.setLoadMoreSupported(hasMoreData(cursor));
final long[] accountIds = getAccountIds();
adapter.setShowAccountsColor(accountIds.length > 1);
setRefreshEnabled(true);
if (accountIds.length > 0) {
showContent();
final ErrorInfoStore.DisplayErrorInfo errorInfo = ErrorInfoStore.getErrorInfo(getContext(),
mErrorInfoStore.get(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountIds[0]));
if (isEmpty && errorInfo != null) {
showError(errorInfo.getIcon(), errorInfo.getMessage());
} else {
showContent();
}
} else {
showError(R.drawable.ic_info_accounts, getString(R.string.no_account_selected));
}

View File

@ -113,6 +113,10 @@ public class AccountPreferences implements Constants {
return mPreferences.getBoolean(KEY_NOTIFICATION_FOLLOWING_ONLY, false);
}
public boolean isNotificationMentionsOnly() {
return mPreferences.getBoolean(KEY_NOTIFICATION_FOLLOWING_ONLY, false);
}
public boolean isNotificationEnabled() {
return mPreferences.getBoolean(KEY_NOTIFICATION, DEFAULT_NOTIFICATION);
}

View File

@ -9,8 +9,16 @@ import org.mariotaku.twidere.model.ParcelableUser;
* Created by mariotaku on 16/1/2.
*/
public class ParcelableActivityUtils {
public static void getAfterFilteredSourceIds(ParcelableActivity activity, long[] filteredUserIds, boolean followingOnly) {
if (activity.after_filtered_source_ids != null) return;
/**
* @param activity Activity for processing
* @param filteredUserIds Those ids will be removed from source_ids.
* @param followingOnly Limit following users in sources
* @return true if source ids changed, false otherwise
*/
public static boolean initAfterFilteredSourceIds(ParcelableActivity activity, long[] filteredUserIds,
boolean followingOnly) {
if (activity.after_filtered_source_ids != null) return false;
if (followingOnly || !ArrayUtils.isEmpty(filteredUserIds)) {
ArrayLongList list = new ArrayLongList();
for (ParcelableUser user : activity.sources) {
@ -22,8 +30,10 @@ public class ParcelableActivityUtils {
}
}
activity.after_filtered_source_ids = list.toArray();
return true;
} else {
activity.after_filtered_source_ids = activity.source_ids;
return false;
}
}

View File

@ -78,6 +78,7 @@ import org.mariotaku.twidere.activity.support.HomeActivity;
import org.mariotaku.twidere.annotation.CustomTabType;
import org.mariotaku.twidere.annotation.NotificationType;
import org.mariotaku.twidere.annotation.ReadPositionTag;
import org.mariotaku.twidere.api.twitter.model.Activity;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.AccountPreferences;
import org.mariotaku.twidere.model.ActivityTitleSummaryMessage;
@ -85,9 +86,10 @@ import org.mariotaku.twidere.model.DraftItem;
import org.mariotaku.twidere.model.DraftItemCursorIndices;
import org.mariotaku.twidere.model.ParcelableActivity;
import org.mariotaku.twidere.model.ParcelableActivityCursorIndices;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.StringLongPair;
import org.mariotaku.twidere.model.UnreadItem;
import org.mariotaku.twidere.model.util.ParcelableActivityUtils;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
import org.mariotaku.twidere.provider.TwidereDataStore.Activities;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags;
@ -135,7 +137,6 @@ import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -1348,18 +1349,28 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
int messageLines = 0;
long timestamp = -1;
while (!c.isAfterLast()) {
c.moveToFirst();
while (c.moveToNext()) {
if (messageLines == 5) {
style.addLine(resources.getString(R.string.and_N_more, count - c.getPosition()));
break;
}
final ParcelableActivity activity = ci.newObject(c);
if (pref.isNotificationMentionsOnly() && ArrayUtils.contains(Activity.Action.MENTION_ACTIONS,
activity.action)) {
continue;
}
final long[] filteredUserIds = DataStoreUtils.getFilteredUserIds(context);
if (timestamp == -1) {
timestamp = activity.timestamp;
}
ParcelableActivityUtils.initAfterFilteredSourceIds(activity, filteredUserIds,
pref.isNotificationFollowingOnly());
final ParcelableUser[] sources = ParcelableActivityUtils.getAfterFilteredSources(activity);
if (ArrayUtils.isEmpty(sources)) continue;
final ActivityTitleSummaryMessage message = ActivityTitleSummaryMessage.get(context,
mUserColorNameManager, activity, activity.sources, 0, false,
mUseStarForLikes, mNameFirst);
mUserColorNameManager, activity, sources,
0, false, mUseStarForLikes, mNameFirst);
if (message != null) {
final CharSequence summary = message.getSummary();
if (TextUtils.isEmpty(summary)) {
@ -1370,7 +1381,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
}
messageLines++;
}
c.moveToNext();
}
builder.setContentIntent(getContentIntent(context, CustomTabType.NOTIFICATIONS_TIMELINE,
NotificationType.INTERACTIONS, accountId));

View File

@ -25,12 +25,12 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.os.AsyncTask;
import android.support.v4.net.ConnectivityManagerCompat;
import android.util.Log;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.util.ConnectivityUtils;
import org.mariotaku.twidere.util.Utils;
import edu.tsinghua.hotmobi.HotMobiLogger;
@ -60,14 +60,16 @@ public class ConnectivityStateReceiver extends BroadcastReceiver implements Cons
HotMobiLogger.getInstance(context).log(event);
// END HotMobi
}
final int networkType = ConnectivityUtils.getActiveNetworkType(context.getApplicationContext());
final boolean isWifi = networkType == ConnectivityManager.TYPE_WIFI;
final boolean isCharging = Utils.isCharging(context.getApplicationContext());
if (isWifi && isCharging) {
final Context appContext = context.getApplicationContext();
final ConnectivityManager cm = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE);
final boolean isNetworkMetered = ConnectivityManagerCompat.isActiveNetworkMetered(cm);
final boolean isCharging = Utils.isCharging(appContext);
if (!isNetworkMetered && isCharging) {
final long currentTime = System.currentTimeMillis();
final long lastSuccessfulTime = HotMobiLogger.getLastUploadTime(context);
final long lastSuccessfulTime = HotMobiLogger.getLastUploadTime(appContext);
if ((currentTime - lastSuccessfulTime) > HotMobiLogger.UPLOAD_INTERVAL_MILLIS) {
AsyncTask.execute(new UploadLogsTask(context.getApplicationContext()));
AsyncTask.execute(new UploadLogsTask(appContext));
}
}

View File

@ -54,6 +54,7 @@ import org.mariotaku.twidere.activity.MainHondaJOJOActivity;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.TwitterUpload;
import org.mariotaku.twidere.api.twitter.model.ErrorInfo;
import org.mariotaku.twidere.api.twitter.model.MediaUploadResponse;
import org.mariotaku.twidere.api.twitter.model.Status;
import org.mariotaku.twidere.api.twitter.model.StatusUpdate;
@ -78,10 +79,8 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Drafts;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.BitmapUtils;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.DebugModeUtils;
import org.mariotaku.twidere.util.MediaUploaderInterface;
import org.mariotaku.twidere.util.NotificationManagerWrapper;
import org.mariotaku.twidere.util.StatusCodeMessageUtils;
import org.mariotaku.twidere.util.StatusShortenerInterface;
import org.mariotaku.twidere.util.TwidereListUtils;
import org.mariotaku.twidere.util.TwidereValidator;
@ -367,7 +366,7 @@ public class BackgroundOperationService extends IntentService implements Constan
// If the status is a duplicate, there's no need to save it to
// drafts.
if (exception instanceof TwitterException
&& ((TwitterException) exception).getErrorCode() == StatusCodeMessageUtils.STATUS_IS_DUPLICATE) {
&& ((TwitterException) exception).getErrorCode() == ErrorInfo.STATUS_IS_DUPLICATE) {
showErrorMessage(getString(R.string.status_is_duplicate), false);
} else {
final ContentValues accountIdsValues = new ContentValues();

View File

@ -47,6 +47,7 @@ import org.mariotaku.twidere.api.twitter.http.HttpResponseCode;
import org.mariotaku.twidere.api.twitter.model.Activity;
import org.mariotaku.twidere.api.twitter.model.CursorTimestampResponse;
import org.mariotaku.twidere.api.twitter.model.DirectMessage;
import org.mariotaku.twidere.api.twitter.model.ErrorInfo;
import org.mariotaku.twidere.api.twitter.model.FriendshipUpdate;
import org.mariotaku.twidere.api.twitter.model.Paging;
import org.mariotaku.twidere.api.twitter.model.Relationship;
@ -114,6 +115,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
private final Bus mBus;
private final UserColorNameManager mUserColorNameManager;
private final ReadStateManager mReadStateManager;
private final ErrorInfoStore mErrorInfoStore;
private int mGetReceivedDirectMessagesTaskId, mGetSentDirectMessagesTaskId;
private int mGetLocalTrendsTaskId;
@ -127,7 +129,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
public AsyncTwitterWrapper(Context context, UserColorNameManager userColorNameManager,
ReadStateManager readStateManager, Bus bus,
SharedPreferencesWrapper preferences, AsyncTaskManager asyncTaskManager) {
SharedPreferencesWrapper preferences, AsyncTaskManager asyncTaskManager, ErrorInfoStore errorInfoStore) {
mContext = context;
mResolver = context.getContentResolver();
mUserColorNameManager = userColorNameManager;
@ -135,6 +137,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
mBus = bus;
mPreferences = preferences;
mAsyncTaskManager = asyncTaskManager;
mErrorInfoStore = errorInfoStore;
}
public int acceptFriendshipAsync(final long accountId, final long userId) {
@ -1465,7 +1468,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
private boolean isMessageNotFound(final Exception e) {
if (!(e instanceof TwitterException)) return false;
final TwitterException te = (TwitterException) e;
return te.getErrorCode() == StatusCodeMessageUtils.PAGE_NOT_FOUND
return te.getErrorCode() == ErrorInfo.PAGE_NOT_FOUND
|| te.getStatusCode() == HttpResponseCode.NOT_FOUND;
}
@ -1523,7 +1526,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
private boolean isMessageNotFound(final Exception e) {
if (!(e instanceof TwitterException)) return false;
final TwitterException te = (TwitterException) e;
return te.getErrorCode() == StatusCodeMessageUtils.PAGE_NOT_FOUND
return te.getErrorCode() == ErrorInfo.PAGE_NOT_FOUND
|| te.getStatusCode() == HttpResponseCode.NOT_FOUND;
}
@ -1948,13 +1951,13 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
if (account_ids == null) return result;
int idx = 0;
final int load_item_limit = mPreferences.getInt(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT);
final int loadItemLimit = mPreferences.getInt(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT);
for (final long accountId : account_ids) {
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(mContext, accountId, true);
if (twitter == null) continue;
try {
final Paging paging = new Paging();
paging.setCount(load_item_limit);
paging.setCount(loadItemLimit);
long max_id = -1, since_id = -1;
if (isMaxIdsValid() && max_ids[idx] > 0) {
max_id = max_ids[idx];
@ -1970,7 +1973,12 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
result.add(new MessageListResponse(accountId, max_id, since_id, messages,
truncated));
storeMessages(accountId, messages, isOutgoing(), true);
mErrorInfoStore.remove(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountId);
} catch (final TwitterException e) {
if (e.getErrorCode() == 93) {
mErrorInfoStore.put(ErrorInfoStore.KEY_DIRECT_MESSAGES, accountId,
ErrorInfoStore.CODE_NO_DM_PERMISSION);
}
if (BuildConfig.DEBUG) {
Log.w(LOGTAG, e);
}

View File

@ -1,44 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
/**
* Created by mariotaku on 15/12/15.
*/
public class ConnectivityUtils {
public static boolean isOnWifi(final Context context) {
if (context == null) return false;
final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo = conn.getActiveNetworkInfo();
return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI
&& networkInfo.isConnected();
}
public static int getActiveNetworkType(final Context context) {
if (context == null) return -1;
final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo = conn.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected() ? networkInfo.getType() : -1;
}
}

View File

@ -75,7 +75,8 @@ public class CustomTabUtils implements Constants {
CUSTOM_TABS_CONFIGURATION_MAP.put(CustomTabType.NOTIFICATIONS_TIMELINE, new CustomTabConfiguration(
ActivitiesAboutMeFragment.class, R.string.notifications, R.drawable.ic_action_notification,
CustomTabConfiguration.ACCOUNT_OPTIONAL, CustomTabConfiguration.FIELD_TYPE_NONE, 1, false,
ExtraConfiguration.newBoolean(EXTRA_MY_FOLLOWING_ONLY, R.string.following_only, false)));
ExtraConfiguration.newBoolean(EXTRA_MY_FOLLOWING_ONLY, R.string.following_only, false),
ExtraConfiguration.newBoolean(EXTRA_MENTIONS_ONLY, R.string.mentions_only, false)));
CUSTOM_TABS_CONFIGURATION_MAP.put(CustomTabType.DIRECT_MESSAGES, new CustomTabConfiguration(
DirectMessagesFragment.class, R.string.direct_messages, R.drawable.ic_action_message,

View File

@ -601,8 +601,9 @@ public class DataStoreUtils implements Constants {
return queryCount(context, uri, selection.getSQL(), null);
}
public static int getActivitiesCount(final Context context, final Uri uri, final long sinceTimestamp,
final long... accountIds) {
public static int getActivitiesCount(final Context context, final Uri uri,
final Expression extraWhere, final String[] extraWhereArgs,
final long sinceTimestamp, final long... accountIds) {
if (context == null) return 0;
final RawItemArray idsIn;
if (accountIds == null || accountIds.length == 0 || (accountIds.length == 1 && accountIds[0] < 0)) {
@ -610,12 +611,18 @@ public class DataStoreUtils implements Constants {
} else {
idsIn = new RawItemArray(accountIds);
}
final Expression selection = Expression.and(
Expression.in(new Columns.Column(Activities.ACCOUNT_ID), idsIn),
Expression.greaterThan(Activities.TIMESTAMP, sinceTimestamp),
buildActivityFilterWhereClause(getTableNameByUri(uri), null)
);
return queryCount(context, uri, selection.getSQL(), null);
Expression[] expressions;
if (extraWhere != null) {
expressions = new Expression[4];
expressions[3] = extraWhere;
} else {
expressions = new Expression[3];
}
expressions[0] = Expression.in(new Columns.Column(Activities.ACCOUNT_ID), idsIn);
expressions[1] = Expression.greaterThan(Activities.TIMESTAMP, sinceTimestamp);
expressions[2] = buildActivityFilterWhereClause(getTableNameByUri(uri), null);
final Expression selection = Expression.and(expressions);
return queryCount(context, uri, selection.getSQL(), extraWhereArgs);
}
public static int getTableId(final Uri uri) {

View File

@ -0,0 +1,85 @@
package org.mariotaku.twidere.util;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.mariotaku.twidere.R;
/**
* Created by mariotaku on 16/1/31.
*/
public class ErrorInfoStore {
public static final String KEY_DIRECT_MESSAGES = "direct_messages";
public static final int CODE_NO_DM_PERMISSION = 1;
private final SharedPreferences mPreferences;
public ErrorInfoStore(Application application) {
mPreferences = application.getSharedPreferences("error_info", Context.MODE_PRIVATE);
}
public int get(String key) {
return mPreferences.getInt(key, 0);
}
public int get(String key, long extraId) {
return get(key + "_" + extraId);
}
public void put(String key, int code) {
mPreferences.edit().putInt(key, code).apply();
}
public void put(String key, long extraId, int code) {
put(key + "_" + extraId, code);
}
@Nullable
public static DisplayErrorInfo getErrorInfo(@NonNull Context context, int code) {
switch (code) {
case CODE_NO_DM_PERMISSION: {
return new DisplayErrorInfo(code, R.drawable.ic_info_error_generic,
context.getString(R.string.error_no_dm_permission));
}
}
return null;
}
public void remove(String key, long extraId) {
remove(key + "_" + extraId);
}
public void remove(String key) {
mPreferences.edit().remove(key).apply();
}
public static class DisplayErrorInfo {
int code;
int icon;
String message;
public DisplayErrorInfo(int code, int icon, String message) {
this.code = code;
this.icon = icon;
this.message = message;
}
public int getCode() {
return code;
}
public int getIcon() {
return icon;
}
public String getMessage() {
return message;
}
}
}

View File

@ -21,6 +21,8 @@ package org.mariotaku.twidere.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.support.v4.net.ConnectivityManagerCompat;
import android.text.TextUtils;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
@ -30,8 +32,6 @@ import org.mariotaku.twidere.Constants;
import java.io.File;
import static org.mariotaku.twidere.util.ConnectivityUtils.isOnWifi;
/**
* @author mariotaku
*/
@ -43,9 +43,11 @@ public class ImagePreloader implements Constants {
private final SharedPreferences mPreferences;
private final DiskCache mDiskCache;
private final ImageLoader mImageLoader;
private final ConnectivityManager mConnectivityManager;
public ImagePreloader(final Context context, final ImageLoader loader) {
mContext = context;
mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
mImageLoader = loader;
mDiskCache = loader.getDiskCache();
@ -64,7 +66,8 @@ public class ImagePreloader implements Constants {
public void preloadImage(final String url) {
if (TextUtils.isEmpty(url)) return;
if (!isOnWifi(mContext) && mPreferences.getBoolean(KEY_PRELOAD_WIFI_ONLY, true)) return;
if (ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager)
&& mPreferences.getBoolean(KEY_PRELOAD_WIFI_ONLY, true)) return;
mImageLoader.loadImage(url, null);
}

View File

@ -23,22 +23,18 @@ import android.content.Context;
import android.util.SparseIntArray;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.api.twitter.model.ErrorInfo;
public class StatusCodeMessageUtils {
public static final int PAGE_NOT_FOUND = 34;
public static final int RATE_LIMIT_EXCEEDED = 88;
public static final int NOT_AUTHORIZED = 179;
public static final int STATUS_IS_DUPLICATE = 187;
private static final SparseIntArray TWITTER_ERROR_CODE_MESSAGES = new SparseIntArray();
private static final SparseIntArray HTTP_STATUS_CODE_MESSAGES = new SparseIntArray();
static {
TWITTER_ERROR_CODE_MESSAGES.put(32, R.string.error_twitter_32);
TWITTER_ERROR_CODE_MESSAGES.put(PAGE_NOT_FOUND, R.string.error_twitter_34);
TWITTER_ERROR_CODE_MESSAGES.put(RATE_LIMIT_EXCEEDED, R.string.error_twitter_88);
TWITTER_ERROR_CODE_MESSAGES.put(ErrorInfo.PAGE_NOT_FOUND, R.string.error_twitter_34);
TWITTER_ERROR_CODE_MESSAGES.put(ErrorInfo.RATE_LIMIT_EXCEEDED, R.string.error_twitter_88);
TWITTER_ERROR_CODE_MESSAGES.put(89, R.string.error_twitter_89);
TWITTER_ERROR_CODE_MESSAGES.put(64, R.string.error_twitter_64);
TWITTER_ERROR_CODE_MESSAGES.put(130, R.string.error_twitter_130);
@ -49,8 +45,8 @@ public class StatusCodeMessageUtils {
TWITTER_ERROR_CODE_MESSAGES.put(161, R.string.error_twitter_161);
TWITTER_ERROR_CODE_MESSAGES.put(162, R.string.error_twitter_162);
TWITTER_ERROR_CODE_MESSAGES.put(172, R.string.error_twitter_172);
TWITTER_ERROR_CODE_MESSAGES.put(NOT_AUTHORIZED, R.string.error_twitter_179);
TWITTER_ERROR_CODE_MESSAGES.put(STATUS_IS_DUPLICATE, R.string.error_twitter_187);
TWITTER_ERROR_CODE_MESSAGES.put(ErrorInfo.NOT_AUTHORIZED, R.string.error_twitter_179);
TWITTER_ERROR_CODE_MESSAGES.put(ErrorInfo.STATUS_IS_DUPLICATE, R.string.error_twitter_187);
TWITTER_ERROR_CODE_MESSAGES.put(193, R.string.error_twitter_193);
TWITTER_ERROR_CODE_MESSAGES.put(215, R.string.error_twitter_215);

View File

@ -44,6 +44,7 @@ import org.mariotaku.twidere.constant.SharedPreferenceConstants;
import org.mariotaku.twidere.util.ActivityTracker;
import org.mariotaku.twidere.util.AsyncTaskManager;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ErrorInfoStore;
import org.mariotaku.twidere.util.ExternalThemeManager;
import org.mariotaku.twidere.util.HttpClientFactory;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
@ -174,9 +175,9 @@ public class ApplicationModule implements Constants {
public AsyncTwitterWrapper asyncTwitterWrapper(UserColorNameManager userColorNameManager,
ReadStateManager readStateManager,
Bus bus, SharedPreferencesWrapper preferences,
AsyncTaskManager asyncTaskManager) {
AsyncTaskManager asyncTaskManager, ErrorInfoStore errorInfoStore) {
return new AsyncTwitterWrapper(application, userColorNameManager, readStateManager, bus,
preferences, asyncTaskManager);
preferences, asyncTaskManager, errorInfoStore);
}
@Provides
@ -215,6 +216,12 @@ public class ApplicationModule implements Constants {
return new TwidereMediaDownloader(application, preferences, client);
}
@Provides
@Singleton
public ErrorInfoStore errorInfoStore() {
return new ErrorInfoStore(application);
}
@Provides
public BidiFormatter provideBidiFormatter() {
return BidiFormatter.getInstance();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

View File

@ -44,7 +44,6 @@
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/element_spacing_large"
android:visibility="gone">
<org.mariotaku.twidere.view.ActionIconView
@ -59,7 +58,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/element_spacing_normal"
android:padding="@dimen/element_spacing_large"
android:textAppearance="?android:textAppearanceMedium"/>
</LinearLayout>

View File

@ -839,4 +839,6 @@
<string name="label_poll">Poll</string>
<string name="poll_summary_format"><xliff:g id="poll_count">%1$s</xliff:g> · <xliff:g id="poll_time_left">%2$s</xliff:g></string>
<string name="title_summary_line_format"><xliff:g id="title">%1$s</xliff:g>: <xliff:g id="summary">%2$s</xliff:g></string>
<string name="mentions_only">Mentions only</string>
<string name="error_no_dm_permission">No direct message permission, check your Twitter application permission setting.</string>
</resources>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.5.1 (25234) - http://www.bohemiancoding.com/sketch -->
<title>ic_action_notification-mdpi</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Action-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="ic_action_notification-mdpi" sketch:type="MSArtboardGroup">
<g id="social_notifications" sketch:type="MSLayerGroup" transform="translate(4.000000, 4.000000)">
<path d="M12,22 C13.1,22 14,21.1 14,20 L10,20 C10,21.1 10.9,22 12,22 L12,22 Z M18.5,16 L18.5,10.5 C18.5,7.43 16.37,4.86 13.5,4.18 L13.5,3.5 C13.5,2.67 12.83,2 12,2 C11.17,2 10.5,2.67 10.5,3.5 L10.5,4.18 C7.63,4.86 5.5,7.43 5.5,10.5 L5.5,16 L3.5,18 L3.5,19 L20.5,19 L20.5,18 L18.5,16 L18.5,16 Z" id="Shape" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>
<path d="M0,0 L24,0 L24,24 L0,24 L0,0 Z" id="Shape" sketch:type="MSShapeGroup"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB