DisplayUtils.java 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * @author Andy Scherzinger
  6. * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
  7. * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. package com.nextcloud.talk.utils;
  23. import android.annotation.SuppressLint;
  24. import android.app.Activity;
  25. import android.content.Context;
  26. import android.content.Intent;
  27. import android.content.res.ColorStateList;
  28. import android.content.res.Configuration;
  29. import android.content.res.Resources;
  30. import android.graphics.Bitmap;
  31. import android.graphics.Canvas;
  32. import android.graphics.Color;
  33. import android.graphics.Typeface;
  34. import android.graphics.drawable.Animatable;
  35. import android.graphics.drawable.BitmapDrawable;
  36. import android.graphics.drawable.Drawable;
  37. import android.graphics.drawable.LayerDrawable;
  38. import android.graphics.drawable.VectorDrawable;
  39. import android.net.Uri;
  40. import android.os.Build;
  41. import android.text.Spannable;
  42. import android.text.SpannableString;
  43. import android.text.Spanned;
  44. import android.text.TextPaint;
  45. import android.text.TextUtils;
  46. import android.text.format.DateUtils;
  47. import android.text.method.LinkMovementMethod;
  48. import android.text.style.AbsoluteSizeSpan;
  49. import android.text.style.ClickableSpan;
  50. import android.text.style.ForegroundColorSpan;
  51. import android.text.style.StyleSpan;
  52. import android.util.Log;
  53. import android.util.TypedValue;
  54. import android.view.View;
  55. import android.view.ViewGroup;
  56. import android.view.Window;
  57. import android.widget.EditText;
  58. import android.widget.TextView;
  59. import com.facebook.common.executors.UiThreadImmediateExecutorService;
  60. import com.facebook.common.references.CloseableReference;
  61. import com.facebook.datasource.DataSource;
  62. import com.facebook.drawee.backends.pipeline.Fresco;
  63. import com.facebook.drawee.controller.ControllerListener;
  64. import com.facebook.drawee.interfaces.DraweeController;
  65. import com.facebook.drawee.view.SimpleDraweeView;
  66. import com.facebook.imagepipeline.common.RotationOptions;
  67. import com.facebook.imagepipeline.core.ImagePipeline;
  68. import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
  69. import com.facebook.imagepipeline.image.CloseableImage;
  70. import com.facebook.imagepipeline.image.ImageInfo;
  71. import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor;
  72. import com.facebook.imagepipeline.postprocessors.RoundPostprocessor;
  73. import com.facebook.imagepipeline.request.ImageRequest;
  74. import com.facebook.imagepipeline.request.ImageRequestBuilder;
  75. import com.facebook.widget.text.span.BetterImageSpan;
  76. import com.google.android.material.chip.ChipDrawable;
  77. import com.nextcloud.talk.R;
  78. import com.nextcloud.talk.application.NextcloudTalkApplication;
  79. import com.nextcloud.talk.data.user.model.User;
  80. import com.nextcloud.talk.events.UserMentionClickEvent;
  81. import com.nextcloud.talk.ui.theme.ViewThemeUtils;
  82. import com.nextcloud.talk.utils.text.Spans;
  83. import org.greenrobot.eventbus.EventBus;
  84. import java.lang.reflect.Constructor;
  85. import java.lang.reflect.InvocationTargetException;
  86. import java.lang.reflect.Method;
  87. import java.text.DateFormat;
  88. import java.util.Date;
  89. import java.util.HashMap;
  90. import java.util.Map;
  91. import java.util.regex.Matcher;
  92. import java.util.regex.Pattern;
  93. import androidx.annotation.ColorInt;
  94. import androidx.annotation.ColorRes;
  95. import androidx.annotation.DrawableRes;
  96. import androidx.annotation.NonNull;
  97. import androidx.annotation.Nullable;
  98. import androidx.annotation.StringRes;
  99. import androidx.annotation.XmlRes;
  100. import androidx.appcompat.widget.AppCompatDrawableManager;
  101. import androidx.core.content.ContextCompat;
  102. import androidx.core.content.res.ResourcesCompat;
  103. import androidx.core.graphics.ColorUtils;
  104. import androidx.core.graphics.drawable.DrawableCompat;
  105. import androidx.emoji.text.EmojiCompat;
  106. import static com.nextcloud.talk.utils.FileSortOrder.sort_a_to_z_id;
  107. import static com.nextcloud.talk.utils.FileSortOrder.sort_big_to_small_id;
  108. import static com.nextcloud.talk.utils.FileSortOrder.sort_new_to_old_id;
  109. import static com.nextcloud.talk.utils.FileSortOrder.sort_old_to_new_id;
  110. import static com.nextcloud.talk.utils.FileSortOrder.sort_small_to_big_id;
  111. import static com.nextcloud.talk.utils.FileSortOrder.sort_z_to_a_id;
  112. public class DisplayUtils {
  113. private static final String TAG = "DisplayUtils";
  114. private static final int INDEX_LUMINATION = 2;
  115. private static final double MAX_LIGHTNESS = 0.92;
  116. private static final String TWITTER_HANDLE_PREFIX = "@";
  117. private static final String HTTP_PROTOCOL = "http://";
  118. private static final String HTTPS_PROTOCOL = "https://";
  119. private static final int DATE_TIME_PARTS_SIZE = 2;
  120. public static void setClickableString(String string, String url, TextView textView) {
  121. SpannableString spannableString = new SpannableString(string);
  122. spannableString.setSpan(new ClickableSpan() {
  123. @Override
  124. public void onClick(@NonNull View widget) {
  125. Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
  126. browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  127. NextcloudTalkApplication
  128. .Companion
  129. .getSharedApplication()
  130. .getApplicationContext()
  131. .startActivity(browserIntent);
  132. }
  133. @Override
  134. public void updateDrawState(@NonNull TextPaint ds) {
  135. super.updateDrawState(ds);
  136. ds.setUnderlineText(false);
  137. }
  138. }, 0, string.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
  139. textView.setText(spannableString);
  140. textView.setMovementMethod(LinkMovementMethod.getInstance());
  141. }
  142. private static void updateViewSize(@Nullable ImageInfo imageInfo, SimpleDraweeView draweeView) {
  143. if (imageInfo != null && draweeView.getId() != R.id.messageUserAvatar) {
  144. int maxSize = draweeView.getContext().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size);
  145. draweeView.getLayoutParams().width = imageInfo.getWidth() > maxSize ? maxSize : imageInfo.getWidth();
  146. draweeView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
  147. draweeView.setAspectRatio((float) imageInfo.getWidth() / imageInfo.getHeight());
  148. draweeView.requestLayout();
  149. }
  150. }
  151. public static Drawable getRoundedDrawable(Drawable drawable) {
  152. Bitmap bitmap = getBitmap(drawable);
  153. new RoundAsCirclePostprocessor(true).process(bitmap);
  154. return new BitmapDrawable(bitmap);
  155. }
  156. public static Bitmap getRoundedBitmapFromVectorDrawableResource(Resources resources, int resource) {
  157. VectorDrawable vectorDrawable = (VectorDrawable) ResourcesCompat.getDrawable(resources, resource, null);
  158. Bitmap bitmap = getBitmap(vectorDrawable);
  159. new RoundPostprocessor(true).process(bitmap);
  160. return bitmap;
  161. }
  162. public static Drawable getRoundedBitmapDrawableFromVectorDrawableResource(Resources resources, int resource) {
  163. return new BitmapDrawable(getRoundedBitmapFromVectorDrawableResource(resources, resource));
  164. }
  165. public static Bitmap getBitmap(Drawable drawable) {
  166. Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
  167. drawable.getIntrinsicHeight(),
  168. Bitmap.Config.ARGB_8888);
  169. Canvas canvas = new Canvas(bitmap);
  170. drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
  171. drawable.draw(canvas);
  172. return bitmap;
  173. }
  174. public static ImageRequest getImageRequestForUrl(String url) {
  175. return getImageRequestForUrl(url, (User) null);
  176. }
  177. public static ImageRequest getImageRequestForUrl(String url, @Nullable User user) {
  178. Map<String, String> headers = new HashMap<>();
  179. if (user != null &&
  180. url.startsWith(user.getBaseUrl()) &&
  181. (url.contains("index.php/core/preview?fileId=") || url.contains("/avatar/"))) {
  182. headers.put("Authorization", ApiUtils.getCredentials(user.getUsername(), user.getToken()));
  183. }
  184. return ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
  185. .setProgressiveRenderingEnabled(true)
  186. .setRotationOptions(RotationOptions.autoRotate())
  187. .disableDiskCache()
  188. .setHeaders(headers)
  189. .build();
  190. }
  191. public static ControllerListener getImageControllerListener(SimpleDraweeView draweeView) {
  192. return new ControllerListener() {
  193. @Override
  194. public void onSubmit(String id, Object callerContext) {
  195. }
  196. @Override
  197. public void onFinalImageSet(String id,
  198. @androidx.annotation.Nullable Object imageInfo,
  199. @androidx.annotation.Nullable Animatable animatable) {
  200. updateViewSize((ImageInfo) imageInfo, draweeView);
  201. }
  202. @Override
  203. public void onIntermediateImageSet(String id, @androidx.annotation.Nullable Object imageInfo) {
  204. updateViewSize((ImageInfo) imageInfo, draweeView);
  205. }
  206. @Override
  207. public void onIntermediateImageFailed(String id, Throwable throwable) {
  208. }
  209. @Override
  210. public void onFailure(String id, Throwable throwable) {
  211. }
  212. @Override
  213. public void onRelease(String id) {
  214. }
  215. };
  216. }
  217. public static float convertDpToPixel(float dp, Context context) {
  218. return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
  219. context.getResources().getDisplayMetrics()) + 0.5f);
  220. }
  221. public static float convertPixelToDp(float px, Context context) {
  222. return px / context.getResources().getDisplayMetrics().density;
  223. }
  224. // Solution inspired by https://stackoverflow.com/questions/34936590/why-isnt-my-vector-drawable-scaling-as-expected
  225. public static void useCompatVectorIfNeeded() {
  226. if (Build.VERSION.SDK_INT < 23) {
  227. try {
  228. @SuppressLint("RestrictedApi") AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
  229. Class<?> inflateDelegateClass = Class.forName(
  230. "android.support.v7.widget.AppCompatDrawableManager$InflateDelegate");
  231. Class<?> vdcInflateDelegateClass = Class.forName(
  232. "android.support.v7.widget.AppCompatDrawableManager$VdcInflateDelegate");
  233. Constructor<?> constructor = vdcInflateDelegateClass.getDeclaredConstructor();
  234. constructor.setAccessible(true);
  235. Object vdcInflateDelegate = constructor.newInstance();
  236. Class<?> args[] = {String.class, inflateDelegateClass};
  237. Method addDelegate = AppCompatDrawableManager.class.getDeclaredMethod("addDelegate", args);
  238. addDelegate.setAccessible(true);
  239. addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate);
  240. } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
  241. InvocationTargetException | IllegalAccessException e) {
  242. Log.e(TAG, "Failed to use reflection to enable proper vector scaling");
  243. }
  244. }
  245. }
  246. public static Drawable getTintedDrawable(Resources res, @DrawableRes int drawableResId, @ColorRes int colorResId) {
  247. Drawable drawable = ResourcesCompat.getDrawable(res, drawableResId, null);
  248. int color = res.getColor(colorResId);
  249. if (drawable != null) {
  250. drawable.setTint(color);
  251. }
  252. return drawable;
  253. }
  254. public static Drawable getDrawableForMentionChipSpan(Context context,
  255. String id,
  256. CharSequence label,
  257. User conversationUser,
  258. String type,
  259. @XmlRes int chipResource,
  260. @Nullable EditText emojiEditText,
  261. ViewThemeUtils viewThemeUtils) {
  262. ChipDrawable chip = ChipDrawable.createFromResource(context, chipResource);
  263. chip.setText(EmojiCompat.get().process(label));
  264. chip.setEllipsize(TextUtils.TruncateAt.MIDDLE);
  265. if (chipResource == R.xml.chip_you) {
  266. viewThemeUtils.material.colorChipDrawable(context, chip);
  267. }
  268. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  269. Configuration config = context.getResources().getConfiguration();
  270. chip.setLayoutDirection(config.getLayoutDirection());
  271. }
  272. int drawable;
  273. boolean isCall = "call".equals(type) || "calls".equals(type);
  274. if (!isCall) {
  275. if (chipResource == R.xml.chip_you) {
  276. drawable = R.drawable.mention_chip;
  277. } else {
  278. drawable = R.drawable.accent_circle;
  279. }
  280. chip.setChipIconResource(drawable);
  281. } else {
  282. chip.setChipIconResource(R.drawable.ic_circular_group);
  283. }
  284. chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight());
  285. if (!isCall) {
  286. String url = ApiUtils.getUrlForAvatar(conversationUser.getBaseUrl(), id, true);
  287. if ("guests".equals(type) || "guest".equals(type)) {
  288. url = ApiUtils.getUrlForGuestAvatar(
  289. conversationUser.getBaseUrl(),
  290. String.valueOf(label), true);
  291. }
  292. ImageRequest imageRequest = getImageRequestForUrl(url);
  293. ImagePipeline imagePipeline = Fresco.getImagePipeline();
  294. DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(
  295. imageRequest,
  296. context);
  297. dataSource.subscribe(
  298. new BaseBitmapDataSubscriber() {
  299. @Override
  300. protected void onNewResultImpl(Bitmap bitmap) {
  301. if (bitmap != null) {
  302. chip.setChipIcon(getRoundedDrawable(new BitmapDrawable(bitmap)));
  303. // A hack to refresh the chip icon
  304. if (emojiEditText != null) {
  305. emojiEditText.post(() -> emojiEditText.setTextKeepState(
  306. emojiEditText.getText(),
  307. TextView.BufferType.SPANNABLE));
  308. }
  309. }
  310. }
  311. @Override
  312. protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
  313. }
  314. },
  315. UiThreadImmediateExecutorService.getInstance());
  316. }
  317. return chip;
  318. }
  319. public static Spannable searchAndReplaceWithMentionSpan(Context context, Spannable text,
  320. String id, String label, String type,
  321. User conversationUser,
  322. @XmlRes int chipXmlRes,
  323. ViewThemeUtils viewThemeUtils) {
  324. Spannable spannableString = new SpannableString(text);
  325. String stringText = text.toString();
  326. Matcher m = Pattern.compile("@" + label,
  327. Pattern.CASE_INSENSITIVE | Pattern.LITERAL | Pattern.MULTILINE)
  328. .matcher(spannableString);
  329. ClickableSpan clickableSpan = new ClickableSpan() {
  330. @Override
  331. public void onClick(@NonNull View widget) {
  332. EventBus.getDefault().post(new UserMentionClickEvent(id));
  333. }
  334. };
  335. int lastStartIndex = -1;
  336. Spans.MentionChipSpan mentionChipSpan;
  337. while (m.find()) {
  338. int start = stringText.indexOf(m.group(), lastStartIndex);
  339. int end = start + m.group().length();
  340. lastStartIndex = end;
  341. mentionChipSpan = new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context,
  342. id,
  343. label,
  344. conversationUser,
  345. type,
  346. chipXmlRes,
  347. null,
  348. viewThemeUtils),
  349. BetterImageSpan.ALIGN_CENTER, id,
  350. label);
  351. spannableString.setSpan(mentionChipSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  352. if (chipXmlRes == R.xml.chip_you) {
  353. spannableString.setSpan(
  354. new ForegroundColorSpan(viewThemeUtils.getScheme(context).getOnPrimary()),
  355. start,
  356. end,
  357. Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  358. }
  359. if ("user".equals(type) && !conversationUser.getUserId().equals(id)) {
  360. spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
  361. }
  362. }
  363. return spannableString;
  364. }
  365. public static Spannable searchAndColor(Spannable text, String searchText, @ColorInt int color) {
  366. Spannable spannableString = new SpannableString(text);
  367. String stringText = text.toString();
  368. if (TextUtils.isEmpty(text) || TextUtils.isEmpty(searchText)) {
  369. return spannableString;
  370. }
  371. Matcher m = Pattern.compile(searchText,
  372. Pattern.CASE_INSENSITIVE | Pattern.LITERAL | Pattern.MULTILINE)
  373. .matcher(spannableString);
  374. int textSize = NextcloudTalkApplication.Companion.getSharedApplication().getResources().getDimensionPixelSize(R.dimen
  375. .chat_text_size);
  376. int lastStartIndex = -1;
  377. while (m.find()) {
  378. int start = stringText.indexOf(m.group(), lastStartIndex);
  379. int end = start + m.group().length();
  380. lastStartIndex = end;
  381. spannableString.setSpan(new ForegroundColorSpan(color), start, end,
  382. Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  383. spannableString.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  384. spannableString.setSpan(new AbsoluteSizeSpan(textSize), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  385. }
  386. return spannableString;
  387. }
  388. public static Drawable getMessageSelector(@ColorInt int normalColor,
  389. @ColorInt int selectedColor,
  390. @ColorInt int pressedColor,
  391. @DrawableRes int shape) {
  392. Drawable vectorDrawable = ContextCompat.getDrawable(NextcloudTalkApplication.Companion.getSharedApplication()
  393. .getApplicationContext(),
  394. shape);
  395. Drawable drawable = DrawableCompat.wrap(vectorDrawable).mutate();
  396. DrawableCompat.setTintList(
  397. drawable,
  398. new ColorStateList(
  399. new int[][]{
  400. new int[]{android.R.attr.state_selected},
  401. new int[]{android.R.attr.state_pressed},
  402. new int[]{-android.R.attr.state_pressed, -android.R.attr.state_selected}
  403. },
  404. new int[]{selectedColor, pressedColor, normalColor}
  405. ));
  406. return drawable;
  407. }
  408. /**
  409. * Sets the color of the status bar to {@code color}.
  410. *
  411. * @param activity activity
  412. * @param color the color
  413. */
  414. public static void applyColorToStatusBar(Activity activity, @ColorInt int color) {
  415. Window window = activity.getWindow();
  416. boolean isLightTheme = lightTheme(color);
  417. if (window != null) {
  418. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  419. View decor = window.getDecorView();
  420. if (isLightTheme) {
  421. int systemUiFlagLightStatusBar;
  422. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  423. systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR |
  424. View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
  425. } else {
  426. systemUiFlagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
  427. }
  428. decor.setSystemUiVisibility(systemUiFlagLightStatusBar);
  429. } else {
  430. decor.setSystemUiVisibility(0);
  431. }
  432. window.setStatusBarColor(color);
  433. } else if (isLightTheme) {
  434. window.setStatusBarColor(Color.BLACK);
  435. }
  436. }
  437. }
  438. /**
  439. * Tests if light color is set
  440. *
  441. * @param color the color
  442. * @return true if primaryColor is lighter than MAX_LIGHTNESS
  443. */
  444. @SuppressWarnings("correctness")
  445. public static boolean lightTheme(int color) {
  446. float[] hsl = colorToHSL(color);
  447. // spotbugs dislikes fixed index access
  448. // which is enforced by having such an
  449. // array from Android-API itself
  450. return hsl[INDEX_LUMINATION] >= MAX_LIGHTNESS;
  451. }
  452. private static float[] colorToHSL(int color) {
  453. float[] hsl = new float[3];
  454. ColorUtils.RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
  455. return hsl;
  456. }
  457. public static void applyColorToNavigationBar(Window window, @ColorInt int color) {
  458. window.setNavigationBarColor(color);
  459. }
  460. /**
  461. * beautifies a given URL by removing any http/https protocol prefix.
  462. *
  463. * @param url to be beautified url
  464. * @return beautified url
  465. */
  466. public static String beautifyURL(@Nullable String url) {
  467. if (TextUtils.isEmpty(url)) {
  468. return "";
  469. }
  470. if (url.length() >= 7 && HTTP_PROTOCOL.equalsIgnoreCase(url.substring(0, 7))) {
  471. return url.substring(HTTP_PROTOCOL.length()).trim();
  472. }
  473. if (url.length() >= 8 && HTTPS_PROTOCOL.equalsIgnoreCase(url.substring(0, 8))) {
  474. return url.substring(HTTPS_PROTOCOL.length()).trim();
  475. }
  476. return url.trim();
  477. }
  478. /**
  479. * beautifies a given twitter handle by prefixing it with an @ in case it is missing.
  480. *
  481. * @param handle to be beautified twitter handle
  482. * @return beautified twitter handle
  483. */
  484. public static String beautifyTwitterHandle(@Nullable String handle) {
  485. if (handle != null) {
  486. String trimmedHandle = handle.trim();
  487. if (TextUtils.isEmpty(trimmedHandle)) {
  488. return "";
  489. }
  490. if (trimmedHandle.startsWith(TWITTER_HANDLE_PREFIX)) {
  491. return trimmedHandle;
  492. } else {
  493. return TWITTER_HANDLE_PREFIX + trimmedHandle;
  494. }
  495. } else {
  496. return "";
  497. }
  498. }
  499. public static void loadAvatarImage(User user, SimpleDraweeView avatarImageView, boolean deleteCache) {
  500. String avatarId;
  501. if (!TextUtils.isEmpty(user.getUserId())) {
  502. avatarId = user.getUserId();
  503. } else {
  504. avatarId = user.getUsername();
  505. }
  506. String avatarString = ApiUtils.getUrlForAvatar(user.getBaseUrl(), avatarId, true);
  507. // clear cache
  508. if (deleteCache) {
  509. Uri avatarUri = Uri.parse(avatarString);
  510. ImagePipeline imagePipeline = Fresco.getImagePipeline();
  511. imagePipeline.evictFromMemoryCache(avatarUri);
  512. imagePipeline.evictFromDiskCache(avatarUri);
  513. imagePipeline.evictFromCache(avatarUri);
  514. }
  515. DraweeController draweeController = Fresco.newDraweeControllerBuilder()
  516. .setOldController(avatarImageView.getController())
  517. .setAutoPlayAnimations(true)
  518. .setImageRequest(DisplayUtils.getImageRequestForUrl(avatarString))
  519. .build();
  520. avatarImageView.setController(draweeController);
  521. }
  522. public static void loadAvatarPlaceholder(final SimpleDraweeView targetView) {
  523. final Context context = targetView.getContext();
  524. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  525. Drawable[] layers = new Drawable[2];
  526. layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background);
  527. layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_foreground);
  528. LayerDrawable layerDrawable = new LayerDrawable(layers);
  529. targetView.getHierarchy().setPlaceholderImage(
  530. DisplayUtils.getRoundedDrawable(layerDrawable));
  531. } else {
  532. targetView.getHierarchy().setPlaceholderImage(R.mipmap.ic_launcher);
  533. }
  534. }
  535. public static void loadImage(final SimpleDraweeView targetView, final ImageRequest imageRequest) {
  536. final DraweeController newController = Fresco.newDraweeControllerBuilder()
  537. .setOldController(targetView.getController())
  538. .setAutoPlayAnimations(true)
  539. .setImageRequest(imageRequest)
  540. .build();
  541. targetView.setController(newController);
  542. }
  543. public static @StringRes
  544. int getSortOrderStringId(FileSortOrder sortOrder) {
  545. switch (sortOrder.getName()) {
  546. case sort_z_to_a_id:
  547. return R.string.menu_item_sort_by_name_z_a;
  548. case sort_new_to_old_id:
  549. return R.string.menu_item_sort_by_date_newest_first;
  550. case sort_old_to_new_id:
  551. return R.string.menu_item_sort_by_date_oldest_first;
  552. case sort_big_to_small_id:
  553. return R.string.menu_item_sort_by_size_biggest_first;
  554. case sort_small_to_big_id:
  555. return R.string.menu_item_sort_by_size_smallest_first;
  556. case sort_a_to_z_id:
  557. default:
  558. return R.string.menu_item_sort_by_name_a_z;
  559. }
  560. }
  561. /**
  562. * calculates the relative time string based on the given modification timestamp.
  563. *
  564. * @param context the app's context
  565. * @param modificationTimestamp the UNIX timestamp of the file modification time in milliseconds.
  566. * @return a relative time string
  567. */
  568. public static CharSequence getRelativeTimestamp(Context context, long modificationTimestamp, boolean showFuture) {
  569. return getRelativeDateTimeString(context,
  570. modificationTimestamp,
  571. android.text.format.DateUtils.SECOND_IN_MILLIS,
  572. DateUtils.WEEK_IN_MILLIS,
  573. 0,
  574. showFuture);
  575. }
  576. public static CharSequence getRelativeDateTimeString(Context c,
  577. long time,
  578. long minResolution,
  579. long transitionResolution,
  580. int flags,
  581. boolean showFuture) {
  582. CharSequence dateString = "";
  583. // in Future
  584. if (!showFuture && time > System.currentTimeMillis()) {
  585. return DisplayUtils.unixTimeToHumanReadable(time);
  586. }
  587. // < 60 seconds -> seconds ago
  588. long diff = System.currentTimeMillis() - time;
  589. if (diff > 0 && diff < 60 * 1000 && minResolution == DateUtils.SECOND_IN_MILLIS) {
  590. return c.getString(R.string.secondsAgo);
  591. } else {
  592. dateString = DateUtils.getRelativeDateTimeString(c, time, minResolution, transitionResolution, flags);
  593. }
  594. String[] parts = dateString.toString().split(",");
  595. if (parts.length == DATE_TIME_PARTS_SIZE) {
  596. if (parts[1].contains(":") && !parts[0].contains(":")) {
  597. return parts[0];
  598. } else if (parts[0].contains(":") && !parts[1].contains(":")) {
  599. return parts[1];
  600. }
  601. }
  602. // dateString contains unexpected format. fallback: use relative date time string from android api as is.
  603. return dateString.toString();
  604. }
  605. /**
  606. * Converts Unix time to human readable format
  607. *
  608. * @param milliseconds that have passed since 01/01/1970
  609. * @return The human readable time for the users locale
  610. */
  611. public static String unixTimeToHumanReadable(long milliseconds) {
  612. Date date = new Date(milliseconds);
  613. DateFormat df = DateFormat.getDateTimeInstance();
  614. return df.format(date);
  615. }
  616. }