diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/APIEditorActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/APIEditorActivity.java index fca7bee79..a647de27b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/APIEditorActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/APIEditorActivity.java @@ -26,8 +26,12 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.AsyncTaskLoader; +import android.support.v4.content.Loader; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -35,17 +39,32 @@ import android.widget.EditText; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.RadioGroup.OnCheckedChangeListener; +import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.AlertDialogWrapper; +import org.mariotaku.restfu.annotation.method.GET; +import org.mariotaku.restfu.http.HttpRequest; +import org.mariotaku.restfu.http.HttpResponse; +import org.mariotaku.restfu.http.RestHttpClient; import org.mariotaku.twidere.R; +import org.mariotaku.twidere.adapter.ArrayAdapter; import org.mariotaku.twidere.fragment.BaseSupportDialogFragment; import org.mariotaku.twidere.model.CustomAPIConfig; import org.mariotaku.twidere.model.ParcelableCredentials; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; +import org.mariotaku.twidere.util.JsonSerializer; import org.mariotaku.twidere.util.ParseUtils; import org.mariotaku.twidere.util.TwitterAPIFactory; +import org.mariotaku.twidere.util.Utils; +import org.mariotaku.twidere.util.dagger.GeneralComponentHelper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import javax.inject.Inject; import static org.mariotaku.twidere.util.Utils.getNonEmptyString; import static org.mariotaku.twidere.util.Utils.trim; @@ -264,26 +283,91 @@ public class APIEditorActivity extends BaseActivity implements OnCheckedChangeLi } public static class LoadDefaultsChooserDialogFragment extends BaseSupportDialogFragment - implements DialogInterface.OnClickListener { - private CustomAPIConfig[] mAPIConfigs; + implements DialogInterface.OnClickListener, LoaderManager.LoaderCallbacks> { + private ArrayAdapter mAdapter; @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Context context = getContext(); - mAPIConfigs = CustomAPIConfig.listDefault(context); + List configs = CustomAPIConfig.listDefault(context); + mAdapter = new CustomAPIConfigArrayAdapter(context, configs); final AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(context); - String[] entries = new String[mAPIConfigs.length]; - for (int i = 0, mAPIConfigsLength = mAPIConfigs.length; i < mAPIConfigsLength; i++) { - entries[i] = mAPIConfigs[i].getLocalizedName(context); - } - builder.setItems(entries, this); + builder.setAdapter(mAdapter, this); + getLoaderManager().initLoader(0, null, this); return builder.create(); } @Override public void onClick(DialogInterface dialog, int which) { - ((APIEditorActivity) getActivity()).setAPIConfig(mAPIConfigs[which]); + ((APIEditorActivity) getActivity()).setAPIConfig(mAdapter.getItem(which)); + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new DefaultAPIConfigLoader(getContext()); + } + + @Override + public void onLoadFinished(Loader> loader, List data) { + if (data != null) { + mAdapter.clear(); + mAdapter.addAll(data); + } + } + + @Override + public void onLoaderReset(Loader> loader) { + + } + + public static class DefaultAPIConfigLoader extends AsyncTaskLoader> { + public static final String DEFAULT_API_CONFIGS_URL = "https://raw.githubusercontent.com/TwidereProject/Twidere-Android/master/twidere/src/main/assets/data/default_api_configs.json"; + @Inject + RestHttpClient mClient; + + public DefaultAPIConfigLoader(Context context) { + super(context); + GeneralComponentHelper.build(context).inject(this); + } + + @Override + public List loadInBackground() { + HttpRequest request = new HttpRequest(GET.METHOD, DEFAULT_API_CONFIGS_URL, + null, null, null); + HttpResponse response = null; + try { + response = mClient.newCall(request).execute(); + if (response.isSuccessful()) { + final InputStream is = response.getBody().stream(); + return JsonSerializer.parseList(is, CustomAPIConfig.class); + } + } catch (IOException e) { + // Ignore + } finally { + Utils.closeSilently(response); + } + return null; + } + + @Override + protected void onStartLoading() { + forceLoad(); + } + } + + private class CustomAPIConfigArrayAdapter extends ArrayAdapter { + public CustomAPIConfigArrayAdapter(Context context, List defaultItems) { + super(context, R.layout.md_listitem, defaultItems); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final View view = super.getView(position, convertView, parent); + CustomAPIConfig config = getItem(position); + ((TextView) view.findViewById(com.afollestad.materialdialogs.R.id.title)).setText(config.getLocalizedName(getContext())); + return view; + } } } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/AccountSelectorActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/AccountSelectorActivity.java index 89f1ddb8b..97d1b6c75 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/AccountSelectorActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/AccountSelectorActivity.java @@ -28,6 +28,7 @@ import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.view.View; import android.view.View.OnClickListener; @@ -237,10 +238,13 @@ public class AccountSelectorActivity extends BaseActivity implements return intent.getBooleanExtra(EXTRA_SELECT_ONLY_ITEM, false); } + @Nullable private Intent getStartIntent() { final Intent intent = getIntent(); final Intent startIntent = intent.getParcelableExtra(EXTRA_START_INTENT); - startIntent.setExtrasClassLoader(TwidereApplication.class.getClassLoader()); + if (startIntent != null) { + startIntent.setExtrasClassLoader(TwidereApplication.class.getClassLoader()); + } return startIntent; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/BaseActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/BaseActivity.java index 325f88045..0419df401 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/BaseActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/BaseActivity.java @@ -208,7 +208,7 @@ public class BaseActivity extends ATEActivity implements Constants, IExtendedAct intentFilter.addDataAuthority("fanfou.com", null); try { adapter.enableForegroundDispatch(this, intent, new IntentFilter[]{intentFilter}, null); - } catch (SecurityException e) { + } catch (Exception e) { // Ignore if blocked by modified roms } } @@ -220,7 +220,7 @@ public class BaseActivity extends ATEActivity implements Constants, IExtendedAct if (adapter != null && adapter.isEnabled()) { try { adapter.disableForegroundDispatch(this); - } catch (SecurityException e) { + } catch (Exception e) { // Ignore if blocked by modified roms } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/ActivityTitleSummaryMessage.java b/twidere/src/main/java/org/mariotaku/twidere/model/ActivityTitleSummaryMessage.java index 968c692aa..bb16855ec 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/model/ActivityTitleSummaryMessage.java +++ b/twidere/src/main/java/org/mariotaku/twidere/model/ActivityTitleSummaryMessage.java @@ -8,6 +8,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import android.text.SpannableString; +import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.StyleSpan; @@ -80,16 +81,9 @@ public class ActivityTitleSummaryMessage { R.string.activity_about_me_like_multi, sources, nameFirst); } } - final StringBuilder summaryBuilder = new StringBuilder(); - boolean first = true; - for (ParcelableStatus status : activity.target_statuses) { - if (!first) { - summaryBuilder.append('\n'); - } - summaryBuilder.append(status.text_unescaped.replace('\n', ' ')); - first = false; - } - return new ActivityTitleSummaryMessage(typeIcon, color, title, summaryBuilder.toString()); + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); + return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.RETWEET: { int typeIcon = R.drawable.ic_activity_action_retweet; @@ -102,7 +96,8 @@ public class ActivityTitleSummaryMessage { title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweet, R.string.activity_about_me_retweet_multi, sources, nameFirst); } - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, + activity.target_object_statuses, nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.FAVORITED_RETWEET: { @@ -121,7 +116,8 @@ public class ActivityTitleSummaryMessage { title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_retweet, R.string.activity_about_me_liked_retweet_multi, sources, nameFirst); } - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.RETWEETED_RETWEET: { @@ -130,7 +126,8 @@ public class ActivityTitleSummaryMessage { int color = ContextCompat.getColor(context, R.color.highlight_retweet); CharSequence title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_retweet, R.string.activity_about_me_retweeted_retweet_multi, sources, nameFirst); - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.RETWEETED_MENTION: { @@ -139,7 +136,8 @@ public class ActivityTitleSummaryMessage { int color = ContextCompat.getColor(context, R.color.highlight_retweet); CharSequence title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_mention, R.string.activity_about_me_retweeted_mention_multi, sources, nameFirst); - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.FAVORITED_MENTION: { @@ -158,7 +156,8 @@ public class ActivityTitleSummaryMessage { title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_mention, R.string.activity_about_me_liked_mention_multi, sources, nameFirst); } - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.LIST_CREATED: { @@ -225,7 +224,8 @@ public class ActivityTitleSummaryMessage { CharSequence title; title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_media_tagged, R.string.activity_about_me_media_tagged_multi, sources, nameFirst); - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.FAVORITED_MEDIA_TAGGED: { @@ -244,7 +244,8 @@ public class ActivityTitleSummaryMessage { title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_liked_media_tagged, R.string.activity_about_me_liked_media_tagged_multi, sources, nameFirst); } - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } case Activity.Action.RETWEETED_MEDIA_TAGGED: { @@ -253,13 +254,33 @@ public class ActivityTitleSummaryMessage { int color = ContextCompat.getColor(context, R.color.highlight_retweet); CharSequence title = getTitleStringAboutMe(resources, manager, R.string.activity_about_me_retweeted_media_tagged, R.string.activity_about_me_retweeted_media_tagged_multi, sources, nameFirst); - final String summary = activity.target_statuses[0].text_unescaped; + final Spanned summary = generateStatusTextSummary(context, activity.target_statuses, + nameFirst); return new ActivityTitleSummaryMessage(typeIcon, color, title, summary); } } return null; } + public static Spanned generateStatusTextSummary(Context context, ParcelableStatus[] statuses, boolean nameFirst) { + if (statuses == null) return null; + final SpannableStringBuilder summaryBuilder = new SpannableStringBuilder(); + boolean first = true; + for (ParcelableStatus status : statuses) { + if (!first) { + summaryBuilder.append('\n'); + } + final SpannableString displayName = new SpannableString(UserColorNameManager.decideDisplayName(status.user_nickname, + status.user_name, status.user_screen_name, nameFirst)); + displayName.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + summaryBuilder.append(SpanFormatter.format(context.getString(R.string.title_summary_line_format), + displayName, status.text_unescaped.replace('\n', ' '))); + first = false; + } + return summaryBuilder; + } + private static Spanned getTitleStringAboutMe(Resources resources, UserColorNameManager manager, int stringRes, int stringResMulti, ParcelableUser[] sources, boolean nameFirst) { diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/CustomAPIConfig.java b/twidere/src/main/java/org/mariotaku/twidere/model/CustomAPIConfig.java index bc1b035bc..519254295 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/model/CustomAPIConfig.java +++ b/twidere/src/main/java/org/mariotaku/twidere/model/CustomAPIConfig.java @@ -16,6 +16,7 @@ import org.mariotaku.twidere.util.Utils; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; import java.util.List; /** @@ -95,14 +96,14 @@ public final class CustomAPIConfig implements Constants { } @NonNull - public static CustomAPIConfig[] listDefault(@NonNull Context context) { + public static List listDefault(@NonNull Context context) { final AssetManager assets = context.getAssets(); InputStream is = null; try { is = assets.open("data/default_api_configs.json"); List configList = JsonSerializer.parseList(is, CustomAPIConfig.class); if (configList == null) return listBuiltin(context); - return configList.toArray(new CustomAPIConfig[configList.size()]); + return configList; } catch (IOException e) { return listBuiltin(context); } finally { @@ -110,10 +111,10 @@ public final class CustomAPIConfig implements Constants { } } - public static CustomAPIConfig[] listBuiltin(@NonNull Context context) { - return new CustomAPIConfig[]{new CustomAPIConfig(context.getString(R.string.provider_default), + public static List listBuiltin(@NonNull Context context) { + return Collections.singletonList(new CustomAPIConfig(context.getString(R.string.provider_default), DEFAULT_TWITTER_API_URL_FORMAT, ParcelableCredentials.AuthType.OAUTH, true, false, - TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)}; + TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)); } static class AuthTypeConverter extends StringBasedTypeConverter { diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/dagger/GeneralComponent.java b/twidere/src/main/java/org/mariotaku/twidere/util/dagger/GeneralComponent.java index e26b4c28d..06536ff5f 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/dagger/GeneralComponent.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/dagger/GeneralComponent.java @@ -21,6 +21,7 @@ package org.mariotaku.twidere.util.dagger; import android.support.v7.widget.RecyclerView; +import org.mariotaku.twidere.activity.APIEditorActivity; import org.mariotaku.twidere.activity.BaseActivity; import org.mariotaku.twidere.activity.ComposeActivity; import org.mariotaku.twidere.activity.MediaViewerActivity; @@ -147,4 +148,6 @@ public interface GeneralComponent { void inject(UpdateProfileBannerImageTask task); void inject(AsyncTwitterWrapper.UpdateProfileImageTask task); + + void inject(APIEditorActivity.LoadDefaultsChooserDialogFragment.DefaultAPIConfigLoader loader); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/support/SpannableStringBuilderSupport.java b/twidere/src/main/java/org/mariotaku/twidere/util/support/SpannableStringBuilderSupport.java new file mode 100644 index 000000000..3b3548748 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/util/support/SpannableStringBuilderSupport.java @@ -0,0 +1,16 @@ +package org.mariotaku.twidere.util.support; + +import android.text.SpannableStringBuilder; + +/** + * Created by mariotaku on 16/4/4. + */ +public class SpannableStringBuilderSupport { + + public static void append(SpannableStringBuilder builder, CharSequence text, Object span, int flags) { + int start = builder.length(); + builder.append(text); + builder.setSpan(span, start, builder.length(), flags); + } + +}