Better toot layouts, char counter in compose
This commit is contained in:
parent
a4a514d37a
commit
b9bdf7caec
|
@ -0,0 +1,349 @@
|
||||||
|
// Copyright 2018 Twitter, Inc.
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
package com.twitter.twittertext;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class Regex {
|
||||||
|
|
||||||
|
protected Regex() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String URL_VALID_GTLD =
|
||||||
|
"(?:(?:" +
|
||||||
|
join(TldLists.GTLDS) +
|
||||||
|
")(?=[^a-z0-9@+-]|$))";
|
||||||
|
private static final String URL_VALID_CCTLD =
|
||||||
|
"(?:(?:" +
|
||||||
|
join(TldLists.CTLDS) +
|
||||||
|
")(?=[^a-z0-9@+-]|$))";
|
||||||
|
|
||||||
|
private static final String INVALID_CHARACTERS =
|
||||||
|
"\\uFFFE" + // BOM
|
||||||
|
"\\uFEFF" + // BOM
|
||||||
|
"\\uFFFF"; // Special
|
||||||
|
|
||||||
|
private static final String DIRECTIONAL_CHARACTERS =
|
||||||
|
"\\u061C" + // ARABIC LETTER MARK (ALM)
|
||||||
|
"\\u200E" + // LEFT-TO-RIGHT MARK (LRM)
|
||||||
|
"\\u200F" + // RIGHT-TO-LEFT MARK (RLM)
|
||||||
|
"\\u202A" + // LEFT-TO-RIGHT EMBEDDING (LRE)
|
||||||
|
"\\u202B" + // RIGHT-TO-LEFT EMBEDDING (RLE)
|
||||||
|
"\\u202C" + // POP DIRECTIONAL FORMATTING (PDF)
|
||||||
|
"\\u202D" + // LEFT-TO-RIGHT OVERRIDE (LRO)
|
||||||
|
"\\u202E" + // RIGHT-TO-LEFT OVERRIDE (RLO)
|
||||||
|
"\\u2066" + // LEFT-TO-RIGHT ISOLATE (LRI)
|
||||||
|
"\\u2067" + // RIGHT-TO-LEFT ISOLATE (RLI)
|
||||||
|
"\\u2068" + // FIRST STRONG ISOLATE (FSI)
|
||||||
|
"\\u2069"; // POP DIRECTIONAL ISOLATE (PDI)
|
||||||
|
|
||||||
|
|
||||||
|
private static final String UNICODE_SPACES = "[" +
|
||||||
|
"\\u0009-\\u000d" + // # White_Space # Cc [5] <control-0009>..<control-000D>
|
||||||
|
"\\u0020" + // White_Space # Zs SPACE
|
||||||
|
"\\u0085" + // White_Space # Cc <control-0085>
|
||||||
|
"\\u00a0" + // White_Space # Zs NO-BREAK SPACE
|
||||||
|
"\\u1680" + // White_Space # Zs OGHAM SPACE MARK
|
||||||
|
"\\u180E" + // White_Space # Zs MONGOLIAN VOWEL SEPARATOR
|
||||||
|
"\\u2000-\\u200a" + // # White_Space # Zs [11] EN QUAD..HAIR SPACE
|
||||||
|
"\\u2028" + // White_Space # Zl LINE SEPARATOR
|
||||||
|
"\\u2029" + // White_Space # Zp PARAGRAPH SEPARATOR
|
||||||
|
"\\u202F" + // White_Space # Zs NARROW NO-BREAK SPACE
|
||||||
|
"\\u205F" + // White_Space # Zs MEDIUM MATHEMATICAL SPACE
|
||||||
|
"\\u3000" + // White_Space # Zs IDEOGRAPHIC SPACE
|
||||||
|
"]";
|
||||||
|
|
||||||
|
private static final String LATIN_ACCENTS_CHARS =
|
||||||
|
// Latin-1
|
||||||
|
"\\u00c0-\\u00d6\\u00d8-\\u00f6\\u00f8-\\u00ff" +
|
||||||
|
// Latin Extended A and B
|
||||||
|
"\\u0100-\\u024f" +
|
||||||
|
// IPA Extensions
|
||||||
|
"\\u0253\\u0254\\u0256\\u0257\\u0259\\u025b\\u0263\\u0268\\u026f\\u0272\\u0289\\u028b" +
|
||||||
|
// Hawaiian
|
||||||
|
"\\u02bb" +
|
||||||
|
// Combining diacritics
|
||||||
|
"\\u0300-\\u036f" +
|
||||||
|
// Latin Extended Additional (mostly for Vietnamese)
|
||||||
|
"\\u1e00-\\u1eff";
|
||||||
|
|
||||||
|
private static final String CYRILLIC_CHARS = "\\u0400-\\u04ff";
|
||||||
|
|
||||||
|
// Generated from unicode_regex/unicode_regex_groups.scala, more inclusive than Java's \p{L}\p{M}
|
||||||
|
private static final String HASHTAG_LETTERS_AND_MARKS = "\\p{L}\\p{M}" +
|
||||||
|
"\\u037f\\u0528-\\u052f\\u08a0-\\u08b2\\u08e4-\\u08ff\\u0978\\u0980\\u0c00\\u0c34\\u0c81" +
|
||||||
|
"\\u0d01\\u0ede\\u0edf\\u10c7\\u10cd\\u10fd-\\u10ff\\u16f1-\\u16f8\\u17b4\\u17b5\\u191d" +
|
||||||
|
"\\u191e\\u1ab0-\\u1abe\\u1bab-\\u1bad\\u1bba-\\u1bbf\\u1cf3-\\u1cf6\\u1cf8\\u1cf9" +
|
||||||
|
"\\u1de7-\\u1df5\\u2cf2\\u2cf3\\u2d27\\u2d2d\\u2d66\\u2d67\\u9fcc\\ua674-\\ua67b\\ua698" +
|
||||||
|
"-\\ua69d\\ua69f\\ua792-\\ua79f\\ua7aa-\\ua7ad\\ua7b0\\ua7b1\\ua7f7-\\ua7f9\\ua9e0-" +
|
||||||
|
"\\ua9ef\\ua9fa-\\ua9fe\\uaa7c-\\uaa7f\\uaae0-\\uaaef\\uaaf2-\\uaaf6\\uab30-\\uab5a" +
|
||||||
|
"\\uab5c-\\uab5f\\uab64\\uab65\\uf870-\\uf87f\\uf882\\uf884-\\uf89f\\uf8b8\\uf8c1-" +
|
||||||
|
"\\uf8d6\\ufa2e\\ufa2f\\ufe27-\\ufe2d\\ud800\\udee0\\ud800\\udf1f\\ud800\\udf50-\\ud800" +
|
||||||
|
"\\udf7a\\ud801\\udd00-\\ud801\\udd27\\ud801\\udd30-\\ud801\\udd63\\ud801\\ude00-\\ud801" +
|
||||||
|
"\\udf36\\ud801\\udf40-\\ud801\\udf55\\ud801\\udf60-\\ud801\\udf67\\ud802\\udc60-\\ud802" +
|
||||||
|
"\\udc76\\ud802\\udc80-\\ud802\\udc9e\\ud802\\udd80-\\ud802\\uddb7\\ud802\\uddbe\\ud802" +
|
||||||
|
"\\uddbf\\ud802\\ude80-\\ud802\\ude9c\\ud802\\udec0-\\ud802\\udec7\\ud802\\udec9-\\ud802" +
|
||||||
|
"\\udee6\\ud802\\udf80-\\ud802\\udf91\\ud804\\udc7f\\ud804\\udcd0-\\ud804\\udce8\\ud804" +
|
||||||
|
"\\udd00-\\ud804\\udd34\\ud804\\udd50-\\ud804\\udd73\\ud804\\udd76\\ud804\\udd80-\\ud804" +
|
||||||
|
"\\uddc4\\ud804\\uddda\\ud804\\ude00-\\ud804\\ude11\\ud804\\ude13-\\ud804\\ude37\\ud804" +
|
||||||
|
"\\udeb0-\\ud804\\udeea\\ud804\\udf01-\\ud804\\udf03\\ud804\\udf05-\\ud804\\udf0c\\ud804" +
|
||||||
|
"\\udf0f\\ud804\\udf10\\ud804\\udf13-\\ud804\\udf28\\ud804\\udf2a-\\ud804\\udf30\\ud804" +
|
||||||
|
"\\udf32\\ud804\\udf33\\ud804\\udf35-\\ud804\\udf39\\ud804\\udf3c-\\ud804\\udf44\\ud804" +
|
||||||
|
"\\udf47\\ud804\\udf48\\ud804\\udf4b-\\ud804\\udf4d\\ud804\\udf57\\ud804\\udf5d-\\ud804" +
|
||||||
|
"\\udf63\\ud804\\udf66-\\ud804\\udf6c\\ud804\\udf70-\\ud804\\udf74\\ud805\\udc80-\\ud805" +
|
||||||
|
"\\udcc5\\ud805\\udcc7\\ud805\\udd80-\\ud805\\uddb5\\ud805\\uddb8-\\ud805\\uddc0\\ud805" +
|
||||||
|
"\\ude00-\\ud805\\ude40\\ud805\\ude44\\ud805\\ude80-\\ud805\\udeb7\\ud806\\udca0-\\ud806" +
|
||||||
|
"\\udcdf\\ud806\\udcff\\ud806\\udec0-\\ud806\\udef8\\ud808\\udf6f-\\ud808\\udf98\\ud81a" +
|
||||||
|
"\\ude40-\\ud81a\\ude5e\\ud81a\\uded0-\\ud81a\\udeed\\ud81a\\udef0-\\ud81a\\udef4\\ud81a" +
|
||||||
|
"\\udf00-\\ud81a\\udf36\\ud81a\\udf40-\\ud81a\\udf43\\ud81a\\udf63-\\ud81a\\udf77\\ud81a" +
|
||||||
|
"\\udf7d-\\ud81a\\udf8f\\ud81b\\udf00-\\ud81b\\udf44\\ud81b\\udf50-\\ud81b\\udf7e\\ud81b" +
|
||||||
|
"\\udf8f-\\ud81b\\udf9f\\ud82f\\udc00-\\ud82f\\udc6a\\ud82f\\udc70-\\ud82f\\udc7c\\ud82f" +
|
||||||
|
"\\udc80-\\ud82f\\udc88\\ud82f\\udc90-\\ud82f\\udc99\\ud82f\\udc9d\\ud82f\\udc9e\\ud83a" +
|
||||||
|
"\\udc00-\\ud83a\\udcc4\\ud83a\\udcd0-\\ud83a\\udcd6\\ud83b\\ude00-\\ud83b\\ude03\\ud83b" +
|
||||||
|
"\\ude05-\\ud83b\\ude1f\\ud83b\\ude21\\ud83b\\ude22\\ud83b\\ude24\\ud83b\\ude27\\ud83b" +
|
||||||
|
"\\ude29-\\ud83b\\ude32\\ud83b\\ude34-\\ud83b\\ude37\\ud83b\\ude39\\ud83b\\ude3b\\ud83b" +
|
||||||
|
"\\ude42\\ud83b\\ude47\\ud83b\\ude49\\ud83b\\ude4b\\ud83b\\ude4d-\\ud83b\\ude4f\\ud83b" +
|
||||||
|
"\\ude51\\ud83b\\ude52\\ud83b\\ude54\\ud83b\\ude57\\ud83b\\ude59\\ud83b\\ude5b\\ud83b" +
|
||||||
|
"\\ude5d\\ud83b\\ude5f\\ud83b\\ude61\\ud83b\\ude62\\ud83b\\ude64\\ud83b\\ude67-\\ud83b" +
|
||||||
|
"\\ude6a\\ud83b\\ude6c-\\ud83b\\ude72\\ud83b\\ude74-\\ud83b\\ude77\\ud83b\\ude79-\\ud83b" +
|
||||||
|
"\\ude7c\\ud83b\\ude7e\\ud83b\\ude80-\\ud83b\\ude89\\ud83b\\ude8b-\\ud83b\\ude9b\\ud83b" +
|
||||||
|
"\\udea1-\\ud83b\\udea3\\ud83b\\udea5-\\ud83b\\udea9\\ud83b\\udeab-\\ud83b\\udebb";
|
||||||
|
|
||||||
|
// Generated from unicode_regex/unicode_regex_groups.scala, more inclusive than Java's \p{Nd}
|
||||||
|
private static final String HASHTAG_NUMERALS = "\\p{Nd}" +
|
||||||
|
"\\u0de6-\\u0def\\ua9f0-\\ua9f9\\ud804\\udcf0-\\ud804\\udcf9\\ud804\\udd36-\\ud804" +
|
||||||
|
"\\udd3f\\ud804\\uddd0-\\ud804\\uddd9\\ud804\\udef0-\\ud804\\udef9\\ud805\\udcd0-\\ud805" +
|
||||||
|
"\\udcd9\\ud805\\ude50-\\ud805\\ude59\\ud805\\udec0-\\ud805\\udec9\\ud806\\udce0-\\ud806" +
|
||||||
|
"\\udce9\\ud81a\\ude60-\\ud81a\\ude69\\ud81a\\udf50-\\ud81a\\udf59";
|
||||||
|
|
||||||
|
private static final String HASHTAG_SPECIAL_CHARS = "_" + //underscore
|
||||||
|
"\\u200c" + // ZERO WIDTH NON-JOINER (ZWNJ)
|
||||||
|
"\\u200d" + // ZERO WIDTH JOINER (ZWJ)
|
||||||
|
"\\ua67e" + // CYRILLIC KAVYKA
|
||||||
|
"\\u05be" + // HEBREW PUNCTUATION MAQAF
|
||||||
|
"\\u05f3" + // HEBREW PUNCTUATION GERESH
|
||||||
|
"\\u05f4" + // HEBREW PUNCTUATION GERSHAYIM
|
||||||
|
"\\uff5e" + // FULLWIDTH TILDE
|
||||||
|
"\\u301c" + // WAVE DASH
|
||||||
|
"\\u309b" + // KATAKANA-HIRAGANA VOICED SOUND MARK
|
||||||
|
"\\u309c" + // KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
|
||||||
|
"\\u30a0" + // KATAKANA-HIRAGANA DOUBLE HYPHEN
|
||||||
|
"\\u30fb" + // KATAKANA MIDDLE DOT
|
||||||
|
"\\u3003" + // DITTO MARK
|
||||||
|
"\\u0f0b" + // TIBETAN MARK INTERSYLLABIC TSHEG
|
||||||
|
"\\u0f0c" + // TIBETAN MARK DELIMITER TSHEG BSTAR
|
||||||
|
"\\u00b7"; // MIDDLE DOT
|
||||||
|
|
||||||
|
private static final String HASHTAG_LETTERS_NUMERALS =
|
||||||
|
HASHTAG_LETTERS_AND_MARKS + HASHTAG_NUMERALS + HASHTAG_SPECIAL_CHARS;
|
||||||
|
private static final String HASHTAG_LETTERS_SET = "[" + HASHTAG_LETTERS_AND_MARKS + "]";
|
||||||
|
private static final String HASHTAG_LETTERS_NUMERALS_SET = "[" + HASHTAG_LETTERS_NUMERALS + "]";
|
||||||
|
|
||||||
|
/* URL related hash regex collection */
|
||||||
|
public static final String URL_VALID_PRECEDING_CHARS =
|
||||||
|
"(?:[^a-z0-9@@$##" + INVALID_CHARACTERS + "]|[" + DIRECTIONAL_CHARACTERS + "]|^)";
|
||||||
|
|
||||||
|
private static final String URL_VALID_CHARS = "[a-z0-9" + LATIN_ACCENTS_CHARS + "]";
|
||||||
|
private static final String URL_VALID_SUBDOMAIN =
|
||||||
|
"(?>(?:" + URL_VALID_CHARS + "[" + URL_VALID_CHARS + "\\-_]*)?" + URL_VALID_CHARS + "\\.)";
|
||||||
|
private static final String URL_VALID_DOMAIN_NAME =
|
||||||
|
"(?:(?:" + URL_VALID_CHARS + "[" + URL_VALID_CHARS + "\\-]*)?" + URL_VALID_CHARS + "\\.)";
|
||||||
|
|
||||||
|
private static final String PUNCTUATION_CHARS = "-_!\"#$%&'\\(\\)*+,./:;<=>?@\\[\\]^`\\{|}~";
|
||||||
|
|
||||||
|
// Any non-space, non-punctuation characters.
|
||||||
|
// \p{Z} = any kind of whitespace or invisible separator.
|
||||||
|
private static final String URL_VALID_UNICODE_CHARS =
|
||||||
|
"[^" + PUNCTUATION_CHARS + "\\s\\p{Z}\\p{InGeneralPunctuation}]";
|
||||||
|
private static final String URL_VALID_UNICODE_DOMAIN_NAME =
|
||||||
|
"(?:(?:" + URL_VALID_UNICODE_CHARS + "[" + URL_VALID_UNICODE_CHARS + "\\-]*)?" +
|
||||||
|
URL_VALID_UNICODE_CHARS + "\\.)";
|
||||||
|
|
||||||
|
private static final String URL_PUNYCODE = "(?:xn--[-0-9a-z]+)";
|
||||||
|
|
||||||
|
public static final String URL_VALID_DOMAIN =
|
||||||
|
"(?:" + // optional sub-domain + domain + TLD
|
||||||
|
URL_VALID_SUBDOMAIN + "*" + URL_VALID_DOMAIN_NAME + // e.g. twitter.com, foo.co.jp ...
|
||||||
|
"(?:" + URL_VALID_GTLD + "|" + URL_VALID_CCTLD + "|" + URL_PUNYCODE + ")" +
|
||||||
|
")" +
|
||||||
|
"|(?:" + "(?<=https?://)" +
|
||||||
|
"(?:" +
|
||||||
|
"(?:" + URL_VALID_DOMAIN_NAME + URL_VALID_CCTLD + ")" + // protocol + domain + ccTLD
|
||||||
|
"|(?:" +
|
||||||
|
URL_VALID_UNICODE_DOMAIN_NAME + // protocol + unicode domain + TLD
|
||||||
|
"(?:" + URL_VALID_GTLD + "|" + URL_VALID_CCTLD + ")" +
|
||||||
|
")" +
|
||||||
|
")" +
|
||||||
|
")" +
|
||||||
|
"|(?:" + // domain + ccTLD + '/'
|
||||||
|
URL_VALID_DOMAIN_NAME + URL_VALID_CCTLD + "(?=/)" + // e.g. t.co/
|
||||||
|
")";
|
||||||
|
|
||||||
|
public static final String URL_VALID_PORT_NUMBER = "[0-9]++";
|
||||||
|
|
||||||
|
private static final String URL_VALID_GENERAL_PATH_CHARS =
|
||||||
|
"[a-z0-9!\\*';:=\\+,.\\$/%#\\[\\]\\-\\u2013_~\\|&@" +
|
||||||
|
LATIN_ACCENTS_CHARS + CYRILLIC_CHARS + "]";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow URL paths to contain up to two nested levels of balanced parens
|
||||||
|
* 1. Used in Wikipedia URLs like /Primer_(film)
|
||||||
|
* 2. Used in IIS sessions like /S(dfd346)/
|
||||||
|
* 3. Used in Rdio URLs like /track/We_Up_(Album_Version_(Edited))/
|
||||||
|
*/
|
||||||
|
private static final String URL_BALANCED_PARENS = "\\(" +
|
||||||
|
"(?:" +
|
||||||
|
URL_VALID_GENERAL_PATH_CHARS + "+" +
|
||||||
|
"|" +
|
||||||
|
// allow one nested level of balanced parentheses
|
||||||
|
"(?:" +
|
||||||
|
URL_VALID_GENERAL_PATH_CHARS + "*" +
|
||||||
|
"\\(" +
|
||||||
|
URL_VALID_GENERAL_PATH_CHARS + "+" +
|
||||||
|
"\\)" +
|
||||||
|
URL_VALID_GENERAL_PATH_CHARS + "*" +
|
||||||
|
")" +
|
||||||
|
")" +
|
||||||
|
"\\)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid end-of-path characters (so /foo. does not gobble the period).
|
||||||
|
* 2. Allow =&# for empty URL parameters and other URL-join artifacts
|
||||||
|
*/
|
||||||
|
private static final String URL_VALID_PATH_ENDING_CHARS =
|
||||||
|
"[a-z0-9=_#/\\-\\+" + LATIN_ACCENTS_CHARS + CYRILLIC_CHARS + "]|(?:" +
|
||||||
|
URL_BALANCED_PARENS + ")";
|
||||||
|
|
||||||
|
public static final String URL_VALID_PATH = "(?:" +
|
||||||
|
"(?:" +
|
||||||
|
URL_VALID_GENERAL_PATH_CHARS + "*" +
|
||||||
|
"(?:" + URL_BALANCED_PARENS + URL_VALID_GENERAL_PATH_CHARS + "*)*" +
|
||||||
|
URL_VALID_PATH_ENDING_CHARS +
|
||||||
|
")|(?:@" + URL_VALID_GENERAL_PATH_CHARS + "+/)" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
public static final String URL_VALID_URL_QUERY_CHARS =
|
||||||
|
"[a-z0-9!?\\*'\\(\\);:&=\\+\\$/%#\\[\\]\\-_\\.,~\\|@]";
|
||||||
|
public static final String URL_VALID_URL_QUERY_ENDING_CHARS = "[a-z0-9\\-_&=#/]";
|
||||||
|
private static final String VALID_URL_PATTERN_STRING =
|
||||||
|
"(" + // $1 total match
|
||||||
|
"(" + URL_VALID_PRECEDING_CHARS + ")" + // $2 Preceding character
|
||||||
|
"(" + // $3 URL
|
||||||
|
"(https?://)?" + // $4 Protocol (optional)
|
||||||
|
"(" + URL_VALID_DOMAIN + ")" + // $5 Domain(s)
|
||||||
|
"(?::(" + URL_VALID_PORT_NUMBER + "))?" + // $6 Port number (optional)
|
||||||
|
"(/" +
|
||||||
|
URL_VALID_PATH + "*+" +
|
||||||
|
")?" + // $7 URL Path and anchor
|
||||||
|
"(\\?" + URL_VALID_URL_QUERY_CHARS + "*" + // $8 Query String
|
||||||
|
URL_VALID_URL_QUERY_ENDING_CHARS + ")?" +
|
||||||
|
")" +
|
||||||
|
")";
|
||||||
|
|
||||||
|
private static final String AT_SIGNS_CHARS = "@\uFF20";
|
||||||
|
private static final String DOLLAR_SIGN_CHAR = "\\$";
|
||||||
|
private static final String CASHTAG = "[a-z]{1,6}(?:[._][a-z]{1,2})?";
|
||||||
|
|
||||||
|
/* Begin public constants */
|
||||||
|
|
||||||
|
public static final Pattern INVALID_CHARACTERS_PATTERN;
|
||||||
|
public static final Pattern VALID_HASHTAG;
|
||||||
|
public static final int VALID_HASHTAG_GROUP_BEFORE = 1;
|
||||||
|
public static final int VALID_HASHTAG_GROUP_HASH = 2;
|
||||||
|
public static final int VALID_HASHTAG_GROUP_TAG = 3;
|
||||||
|
public static final Pattern INVALID_HASHTAG_MATCH_END;
|
||||||
|
public static final Pattern RTL_CHARACTERS;
|
||||||
|
|
||||||
|
public static final Pattern AT_SIGNS;
|
||||||
|
public static final Pattern VALID_MENTION_OR_LIST;
|
||||||
|
public static final int VALID_MENTION_OR_LIST_GROUP_BEFORE = 1;
|
||||||
|
public static final int VALID_MENTION_OR_LIST_GROUP_AT = 2;
|
||||||
|
public static final int VALID_MENTION_OR_LIST_GROUP_USERNAME = 3;
|
||||||
|
public static final int VALID_MENTION_OR_LIST_GROUP_LIST = 4;
|
||||||
|
|
||||||
|
public static final Pattern VALID_REPLY;
|
||||||
|
public static final int VALID_REPLY_GROUP_USERNAME = 1;
|
||||||
|
|
||||||
|
public static final Pattern INVALID_MENTION_MATCH_END;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to extract URL (it also includes the text preceding the url).
|
||||||
|
*
|
||||||
|
* This regex does not reflect its name and {@link Regex#VALID_URL_GROUP_URL} match
|
||||||
|
* should be checked in order to match a valid url. This is not ideal, but the behavior is
|
||||||
|
* being kept to ensure backwards compatibility. Ideally this regex should be
|
||||||
|
* implemented with a negative lookbehind as opposed to a negated character class
|
||||||
|
* but lack of JS support increases maint overhead if the logic is different by
|
||||||
|
* platform.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static final Pattern VALID_URL;
|
||||||
|
public static final int VALID_URL_GROUP_ALL = 1;
|
||||||
|
public static final int VALID_URL_GROUP_BEFORE = 2;
|
||||||
|
public static final int VALID_URL_GROUP_URL = 3;
|
||||||
|
public static final int VALID_URL_GROUP_PROTOCOL = 4;
|
||||||
|
public static final int VALID_URL_GROUP_DOMAIN = 5;
|
||||||
|
public static final int VALID_URL_GROUP_PORT = 6;
|
||||||
|
public static final int VALID_URL_GROUP_PATH = 7;
|
||||||
|
public static final int VALID_URL_GROUP_QUERY_STRING = 8;
|
||||||
|
|
||||||
|
public static final Pattern VALID_TCO_URL;
|
||||||
|
public static final Pattern INVALID_URL_WITHOUT_PROTOCOL_MATCH_BEGIN;
|
||||||
|
|
||||||
|
public static final Pattern VALID_CASHTAG;
|
||||||
|
public static final int VALID_CASHTAG_GROUP_BEFORE = 1;
|
||||||
|
public static final int VALID_CASHTAG_GROUP_DOLLAR = 2;
|
||||||
|
public static final int VALID_CASHTAG_GROUP_CASHTAG = 3;
|
||||||
|
|
||||||
|
public static final Pattern VALID_DOMAIN;
|
||||||
|
|
||||||
|
// initializing in a static synchronized block,
|
||||||
|
// there appears to be thread safety issues with Pattern.compile in android
|
||||||
|
static {
|
||||||
|
synchronized (Regex.class) {
|
||||||
|
INVALID_CHARACTERS_PATTERN = Pattern.compile(".*[" + INVALID_CHARACTERS + "].*");
|
||||||
|
VALID_HASHTAG = Pattern.compile("(^|\\uFE0E|\\uFE0F|[^&" + HASHTAG_LETTERS_NUMERALS +
|
||||||
|
"])([#\uFF03])(?![\uFE0F\u20E3])(" + HASHTAG_LETTERS_NUMERALS_SET + "*" +
|
||||||
|
HASHTAG_LETTERS_SET + HASHTAG_LETTERS_NUMERALS_SET + "*)", Pattern.CASE_INSENSITIVE);
|
||||||
|
INVALID_HASHTAG_MATCH_END = Pattern.compile("^(?:[##]|://)");
|
||||||
|
RTL_CHARACTERS = Pattern.compile("[\u0600-\u06FF\u0750-\u077F\u0590-\u05FF\uFE70-\uFEFF]");
|
||||||
|
AT_SIGNS = Pattern.compile("[" + AT_SIGNS_CHARS + "]");
|
||||||
|
VALID_MENTION_OR_LIST = Pattern.compile("([^a-z0-9_!#$%&*" + AT_SIGNS_CHARS +
|
||||||
|
"]|^|(?:^|[^a-z0-9_+~.-])RT:?)(" + AT_SIGNS +
|
||||||
|
"+)([a-z0-9_]{1,20})(/[a-z][a-z0-9_\\-]{0,24})?", Pattern.CASE_INSENSITIVE);
|
||||||
|
VALID_REPLY = Pattern.compile("^(?:" + UNICODE_SPACES + "|" + DIRECTIONAL_CHARACTERS + ")*" +
|
||||||
|
AT_SIGNS + "([a-z0-9_]{1,20})", Pattern.CASE_INSENSITIVE);
|
||||||
|
INVALID_MENTION_MATCH_END =
|
||||||
|
Pattern.compile("^(?:[" + AT_SIGNS_CHARS + LATIN_ACCENTS_CHARS + "]|://)");
|
||||||
|
INVALID_URL_WITHOUT_PROTOCOL_MATCH_BEGIN = Pattern.compile("[-_./]$");
|
||||||
|
|
||||||
|
VALID_URL = Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
|
||||||
|
VALID_TCO_URL = Pattern.compile("^https?://t\\.co/([a-z0-9]+)(?:\\?" +
|
||||||
|
URL_VALID_URL_QUERY_CHARS + "*" + URL_VALID_URL_QUERY_ENDING_CHARS + ")?",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
VALID_CASHTAG = Pattern.compile("(^|" + UNICODE_SPACES + "|" + DIRECTIONAL_CHARACTERS + ")(" +
|
||||||
|
DOLLAR_SIGN_CHAR + ")(" + CASHTAG + ")" + "(?=$|\\s|\\p{Punct})",
|
||||||
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
VALID_DOMAIN = Pattern.compile(URL_VALID_DOMAIN, Pattern.CASE_INSENSITIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String join(@NonNull Collection<?> col) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final Iterator<?> iter = col.iterator();
|
||||||
|
if (iter.hasNext()) {
|
||||||
|
sb.append(iter.next().toString());
|
||||||
|
}
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
sb.append("|");
|
||||||
|
sb.append(iter.next().toString());
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,8 @@
|
||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -9,8 +11,10 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.PhotoStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.PhotoStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
|
@ -26,6 +30,7 @@ import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost{
|
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost{
|
||||||
|
@ -204,6 +209,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
currentPhotoViewer.offsetView(-dx, -dy);
|
currentPhotoViewer.offsetView(-dx, -dy);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
|
private Paint paint=new Paint();
|
||||||
|
{
|
||||||
|
paint.setColor(0xFFD0D5DD);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setStrokeWidth(V.dp(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
|
for(int i=0;i<parent.getChildCount();i++){
|
||||||
|
View child=parent.getChildAt(i);
|
||||||
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||||
|
if(holder instanceof FooterStatusDisplayItem.Holder){
|
||||||
|
float y=child.getY()+child.getHeight()-V.dp(.5f);
|
||||||
|
c.drawLine(child.getX(), y, child.getX()+child.getWidth(), y, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getMainAdapterOffset(){
|
protected int getMainAdapterOffset(){
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Outline;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewOutlineProvider;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.twitter.twittertext.Regex;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
import java.text.BreakIterator;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class ComposeFragment extends ToolbarFragment{
|
||||||
|
|
||||||
|
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private static final String VALID_URL_PATTERN_STRING =
|
||||||
|
"(" + // $1 total match
|
||||||
|
"(" + Regex.URL_VALID_PRECEDING_CHARS + ")" + // $2 Preceding character
|
||||||
|
"(" + // $3 URL
|
||||||
|
"(https?://)" + // $4 Protocol (optional)
|
||||||
|
"(" + Regex.URL_VALID_DOMAIN + ")" + // $5 Domain(s)
|
||||||
|
"(?::(" + Regex.URL_VALID_PORT_NUMBER + "))?" + // $6 Port number (optional)
|
||||||
|
"(/" +
|
||||||
|
Regex.URL_VALID_PATH + "*+" +
|
||||||
|
")?" + // $7 URL Path and anchor
|
||||||
|
"(\\?" + Regex.URL_VALID_URL_QUERY_CHARS + "*" + // $8 Query String
|
||||||
|
Regex.URL_VALID_URL_QUERY_ENDING_CHARS + ")?" +
|
||||||
|
")" +
|
||||||
|
")";
|
||||||
|
private static final Pattern URL_PATTERN=Pattern.compile(VALID_URL_PATTERN_STRING, Pattern.CASE_INSENSITIVE);
|
||||||
|
private final BreakIterator breakIterator=BreakIterator.getCharacterInstance();
|
||||||
|
|
||||||
|
private TextView selfName, selfUsername;
|
||||||
|
private ImageView selfAvatar;
|
||||||
|
private Account self;
|
||||||
|
private String instanceDomain;
|
||||||
|
|
||||||
|
private EditText mainEditText;
|
||||||
|
private TextView charCounter;
|
||||||
|
private String accountID;
|
||||||
|
private int charCount, charLimit;
|
||||||
|
|
||||||
|
private MenuItem publishButton;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity){
|
||||||
|
super.onAttach(activity);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
charLimit=session.tootCharLimit;
|
||||||
|
if(charLimit==0)
|
||||||
|
charLimit=500;
|
||||||
|
self=session.self;
|
||||||
|
instanceDomain=session.domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
|
View view=inflater.inflate(R.layout.fragment_compose, container, false);
|
||||||
|
mainEditText=view.findViewById(R.id.toot_text);
|
||||||
|
charCounter=view.findViewById(R.id.char_counter);
|
||||||
|
charCounter.setText(String.valueOf(charLimit));
|
||||||
|
|
||||||
|
selfName=view.findViewById(R.id.name);
|
||||||
|
selfUsername=view.findViewById(R.id.username);
|
||||||
|
selfAvatar=view.findViewById(R.id.avatar);
|
||||||
|
selfName.setText(self.displayName);
|
||||||
|
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
||||||
|
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
||||||
|
ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
|
||||||
|
@Override
|
||||||
|
public void getOutline(View view, Outline outline){
|
||||||
|
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), V.dp(12));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
selfAvatar.setOutlineProvider(roundCornersOutline);
|
||||||
|
selfAvatar.setClipToOutline(true);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume(){
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
||||||
|
view.postDelayed(()->{
|
||||||
|
mainEditText.requestFocus();
|
||||||
|
imm.showSoftInput(mainEditText, 0);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
mainEditText.addTextChangedListener(new TextWatcher(){
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s){
|
||||||
|
updateCharCounter(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
|
publishButton=menu.add("TOOT!");
|
||||||
|
publishButton.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
updatePublishButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
|
String text=mainEditText.getText().toString();
|
||||||
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
|
req.status=text;
|
||||||
|
String uuid=UUID.randomUUID().toString();
|
||||||
|
new CreateStatus(req, uuid)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status result){
|
||||||
|
Nav.finish(ComposeFragment.this);
|
||||||
|
E.post(new StatusCreatedEvent(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCharCounter(CharSequence text){
|
||||||
|
String countableText=MENTION_PATTERN.matcher(URL_PATTERN.matcher(text).replaceAll("$2xxxxxxxxxxxxxxxxxxxxxxx")).replaceAll("$1@$3");
|
||||||
|
breakIterator.setText(countableText);
|
||||||
|
charCount=0;
|
||||||
|
while(breakIterator.next()!=BreakIterator.DONE){
|
||||||
|
charCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
charCounter.setText(String.valueOf(charLimit-charCount));
|
||||||
|
updatePublishButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePublishButtonState(){
|
||||||
|
publishButton.setEnabled(charCount>0 && charCount<=charLimit);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,88 +0,0 @@
|
||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
|
||||||
|
|
||||||
public class CreateTootFragment extends ToolbarFragment{
|
|
||||||
|
|
||||||
private EditText mainEditText;
|
|
||||||
private String accountID;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity){
|
|
||||||
super.onAttach(activity);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
accountID=getArguments().getString("account");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
|
||||||
View view=inflater.inflate(R.layout.fragment_new_toot, container, false);
|
|
||||||
mainEditText=view.findViewById(R.id.toot_text);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume(){
|
|
||||||
super.onResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
|
||||||
view.postDelayed(()->{
|
|
||||||
mainEditText.requestFocus();
|
|
||||||
imm.showSoftInput(mainEditText, 0);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
|
||||||
menu.add("TOOT!").setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
|
||||||
String text=mainEditText.getText().toString();
|
|
||||||
CreateStatus.Request req=new CreateStatus.Request();
|
|
||||||
req.status=text;
|
|
||||||
String uuid=UUID.randomUUID().toString();
|
|
||||||
new CreateStatus(req, uuid)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Status result){
|
|
||||||
Nav.finish(CreateTootFragment.this);
|
|
||||||
E.post(new StatusCreatedEvent(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -49,4 +49,9 @@ public class HomeFragment extends AppKitFragment{
|
||||||
super.onHiddenChanged(hidden);
|
super.onHiddenChanged(hidden);
|
||||||
homeTimelineFragment.onHiddenChanged(hidden);
|
homeTimelineFragment.onHiddenChanged(hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightStatusBar(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
@ -23,6 +25,11 @@ import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class HomeTimelineFragment extends StatusListFragment{
|
public class HomeTimelineFragment extends StatusListFragment{
|
||||||
|
private ImageButton fab;
|
||||||
|
|
||||||
|
public HomeTimelineFragment(){
|
||||||
|
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
|
@ -44,6 +51,13 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
fab=view.findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.home, menu);
|
inflater.inflate(R.menu.home, menu);
|
||||||
|
@ -55,7 +69,7 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
int id=item.getItemId();
|
int id=item.getItemId();
|
||||||
if(id==R.id.new_toot){
|
if(id==R.id.new_toot){
|
||||||
Nav.go(getActivity(), CreateTootFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}else if(id==R.id.notifications){
|
}else if(id==R.id.notifications){
|
||||||
Nav.go(getActivity(), NotificationsFragment.class, args);
|
Nav.go(getActivity(), NotificationsFragment.class, args);
|
||||||
}else if(id==R.id.my_profile){
|
}else if(id==R.id.my_profile){
|
||||||
|
@ -81,4 +95,10 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||||
public void onStatusCreated(StatusCreatedEvent ev){
|
public void onStatusCreated(StatusCreatedEvent ev){
|
||||||
prependItems(Collections.singletonList(ev.status));
|
prependItems(Collections.singletonList(ev.status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onFabClick(View v){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||||
|
private final Status status;
|
||||||
|
private final String accountID;
|
||||||
|
|
||||||
|
public FooterStatusDisplayItem(String parentID, Status status, String accountID){
|
||||||
|
super(parentID);
|
||||||
|
this.status=status;
|
||||||
|
this.accountID=accountID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getType(){
|
||||||
|
return Type.FOOTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Holder extends BindableViewHolder<FooterStatusDisplayItem>{
|
||||||
|
private final TextView reply, boost, favorite;
|
||||||
|
private final ImageView share;
|
||||||
|
|
||||||
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
|
super(activity, R.layout.display_item_footer, parent);
|
||||||
|
reply=findViewById(R.id.reply);
|
||||||
|
boost=findViewById(R.id.boost);
|
||||||
|
favorite=findViewById(R.id.favorite);
|
||||||
|
share=findViewById(R.id.share);
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||||
|
UiUtils.fixCompoundDrawableTintOnAndroid6(reply, R.color.text_secondary);
|
||||||
|
UiUtils.fixCompoundDrawableTintOnAndroid6(boost, R.color.text_secondary);
|
||||||
|
UiUtils.fixCompoundDrawableTintOnAndroid6(favorite, R.color.text_secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(FooterStatusDisplayItem item){
|
||||||
|
bindButton(reply, item.status.repliesCount);
|
||||||
|
bindButton(boost, item.status.reblogsCount);
|
||||||
|
bindButton(favorite, item.status.favouritesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindButton(TextView btn, int count){
|
||||||
|
if(count>0){
|
||||||
|
btn.setText(DecimalFormat.getIntegerInstance().format(count));
|
||||||
|
btn.setCompoundDrawablePadding(V.dp(8));
|
||||||
|
}else{
|
||||||
|
btn.setText("");
|
||||||
|
btn.setCompoundDrawablePadding(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,18 +2,21 @@ package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
import android.graphics.Outline;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewOutlineProvider;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
@ -23,6 +26,7 @@ import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||||
private Account user;
|
private Account user;
|
||||||
|
@ -56,20 +60,34 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Holder extends BindableViewHolder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
public static class Holder extends BindableViewHolder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
private final TextView name, subtitle;
|
private final TextView name, username, timestamp;
|
||||||
private final ImageView avatar;
|
private final ImageView avatar, more;
|
||||||
|
|
||||||
|
private static final ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
|
||||||
|
@Override
|
||||||
|
public void getOutline(View view, Outline outline){
|
||||||
|
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), V.dp(12));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_header, parent);
|
super(activity, R.layout.display_item_header, parent);
|
||||||
name=findViewById(R.id.name);
|
name=findViewById(R.id.name);
|
||||||
subtitle=findViewById(R.id.subtitle);
|
username=findViewById(R.id.username);
|
||||||
|
timestamp=findViewById(R.id.timestamp);
|
||||||
avatar=findViewById(R.id.avatar);
|
avatar=findViewById(R.id.avatar);
|
||||||
|
more=findViewById(R.id.more);
|
||||||
avatar.setOnClickListener(this::onAvaClick);
|
avatar.setOnClickListener(this::onAvaClick);
|
||||||
|
avatar.setOutlineProvider(roundCornersOutline);
|
||||||
|
avatar.setClipToOutline(true);
|
||||||
|
more.setOnClickListener(this::onMoreClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(HeaderStatusDisplayItem item){
|
public void onBind(HeaderStatusDisplayItem item){
|
||||||
name.setText(item.user.displayName);
|
name.setText(item.user.displayName);
|
||||||
subtitle.setText('@'+item.user.acct);
|
username.setText('@'+item.user.acct);
|
||||||
|
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -90,5 +108,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(item.user));
|
args.putParcelable("profileAccount", Parcels.wrap(item.user));
|
||||||
Nav.go(item.parentFragment.getActivity(), ProfileFragment.class, args);
|
Nav.go(item.parentFragment.getActivity(), ProfileFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onMoreClick(View v){
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.os.Build;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
|
||||||
|
@ -27,6 +28,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
|
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
||||||
|
UiUtils.fixCompoundDrawableTintOnAndroid6(text, R.color.text_secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -39,6 +39,7 @@ public abstract class StatusDisplayItem{
|
||||||
case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent);
|
case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent);
|
||||||
case TEXT -> new TextStatusDisplayItem.Holder(activity, parent);
|
case TEXT -> new TextStatusDisplayItem.Holder(activity, parent);
|
||||||
case PHOTO -> new PhotoStatusDisplayItem.Holder(activity, parent);
|
case PHOTO -> new PhotoStatusDisplayItem.Holder(activity, parent);
|
||||||
|
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
|
||||||
default -> throw new UnsupportedOperationException();
|
default -> throw new UnsupportedOperationException();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -58,6 +59,7 @@ public abstract class StatusDisplayItem{
|
||||||
items.add(new PhotoStatusDisplayItem(parentID, status, attachment, fragment));
|
items.add(new PhotoStatusDisplayItem(parentID, status, attachment, fragment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
items.add(new FooterStatusDisplayItem(parentID, status, accountID));
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
package org.joinmastodon.android.ui.utils;
|
package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorRes;
|
||||||
import androidx.browser.customtabs.CustomTabsIntent;
|
import androidx.browser.customtabs.CustomTabsIntent;
|
||||||
|
|
||||||
public class UiUtils{
|
public class UiUtils{
|
||||||
|
@ -14,4 +23,37 @@ public class UiUtils{
|
||||||
.build()
|
.build()
|
||||||
.launchUrl(context, Uri.parse(url));
|
.launchUrl(context, Uri.parse(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatRelativeTimestamp(Context context, Instant instant){
|
||||||
|
long t=instant.toEpochMilli();
|
||||||
|
long now=System.currentTimeMillis();
|
||||||
|
long diff=now-t;
|
||||||
|
if(diff<60_000L){
|
||||||
|
return context.getString(R.string.time_seconds, diff/1000L);
|
||||||
|
}else if(diff<3600_000L){
|
||||||
|
return context.getString(R.string.time_minutes, diff/60_000L);
|
||||||
|
}else if(diff<3600_000L*24L){
|
||||||
|
return context.getString(R.string.time_hours, diff/3600_000L);
|
||||||
|
}else{
|
||||||
|
return context.getString(R.string.time_days, diff/(3600_000L*24L));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android 6.0 has a bug where start and end compound drawables don't get tinted.
|
||||||
|
* This works around it by setting the tint colors directly to the drawables.
|
||||||
|
* @param textView
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void fixCompoundDrawableTintOnAndroid6(TextView textView, @ColorRes int color){
|
||||||
|
Drawable[] drawables=textView.getCompoundDrawablesRelative();
|
||||||
|
for(int i=0;i<drawables.length;i++){
|
||||||
|
if(drawables[i]!=null){
|
||||||
|
Drawable tinted=drawables[i].mutate();
|
||||||
|
tinted.setTintList(textView.getContext().getColorStateList(color));
|
||||||
|
drawables[i]=tinted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textView.setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.joinmastodon.android.ui.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A LinearLayout for TextViews. First child TextView will get truncated if it doesn't fit, remaining will always wrap content.
|
||||||
|
*/
|
||||||
|
public class HeaderSubtitleLinearLayout extends LinearLayout{
|
||||||
|
public HeaderSubtitleLinearLayout(Context context){
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HeaderSubtitleLinearLayout(Context context, AttributeSet attrs){
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HeaderSubtitleLinearLayout(Context context, AttributeSet attrs, int defStyleAttr){
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||||
|
if(getChildCount()>1){
|
||||||
|
int remainingWidth=MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
for(int i=1;i<getChildCount();i++){
|
||||||
|
View v=getChildAt(i);
|
||||||
|
v.measure(MeasureSpec.getSize(widthMeasureSpec) | MeasureSpec.AT_MOST, heightMeasureSpec);
|
||||||
|
LayoutParams lp=(LayoutParams) v.getLayoutParams();
|
||||||
|
remainingWidth-=v.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
|
||||||
|
}
|
||||||
|
View first=getChildAt(0);
|
||||||
|
if(first instanceof TextView){
|
||||||
|
((TextView) first).setMaxWidth(remainingWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<set>
|
||||||
|
<objectAnimator android:propertyName="translationZ"
|
||||||
|
android:duration="@android:integer/config_shortAnimTime"
|
||||||
|
android:valueTo="4dp"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
</set>
|
||||||
|
</item>
|
||||||
|
<item android:state_pressed="false">
|
||||||
|
<set>
|
||||||
|
<objectAnimator android:propertyName="translationZ"
|
||||||
|
android:duration="100"
|
||||||
|
android:valueTo="2dp"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
</set>
|
||||||
|
</item>
|
||||||
|
</selector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="?android:colorControlHighlight">
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="@color/secondary"/>
|
||||||
|
<corners android:radius="16dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="34dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="34dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||||
|
<path android:pathData="M15.75 6.01c0.18 0 0.344 0.063 0.473 0.168 1.088 0.914 1.78 2.287 1.78 3.822 0 2.689-2.122 4.882-4.783 4.995L13.003 15H8.56l1.22 1.22c0.266 0.266 0.29 0.683 0.072 0.976L9.781 17.28c-0.267 0.267-0.683 0.29-0.977 0.073L8.72 17.28l-2.5-2.5c-0.266-0.266-0.29-0.683-0.072-0.976L6.22 13.72l2.5-2.5c0.293-0.293 0.768-0.293 1.06 0 0.267 0.266 0.291 0.683 0.073 0.976L9.781 12.28 8.56 13.5h4.442c1.868 0 3.395-1.464 3.495-3.308L16.503 10c0-1.081-0.49-2.048-1.26-2.69C15.093 7.175 15 6.98 15 6.76c0-0.414 0.335-0.75 0.75-0.75zm-5.53-3.29c0.266-0.267 0.683-0.29 0.977-0.073L11.28 2.72l2.5 2.5 0.072 0.084c0.194 0.26 0.197 0.619 0.008 0.882L13.78 6.28l-2.5 2.5-0.084 0.073c-0.261 0.194-0.62 0.196-0.883 0.007L10.22 8.78l-0.072-0.084C9.954 8.436 9.95 8.077 10.14 7.814l0.08-0.094 1.22-1.22H7c-1.87 0-3.396 1.464-3.496 3.308L3.498 10c0 1.083 0.492 2.051 1.265 2.693C4.909 12.83 5 13.023 5 13.24c0 0.414-0.335 0.75-0.75 0.75-0.19 0-0.365-0.072-0.498-0.19-1.073-0.913-1.754-2.277-1.754-3.8 0-2.689 2.122-4.882 4.783-4.995L6.998 5h4.441L10.22 3.78l-0.072-0.084C9.93 3.403 9.954 2.986 10.22 2.72z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M14.61 2.47l-0.077-0.067C14.24 2.18 13.818 2.201 13.55 2.47l-0.068 0.077c-0.223 0.296-0.201 0.718 0.068 0.988l1.971 1.977H8.5L8.267 5.516C4.785 5.639 2 8.51 2 12.036c0 1.69 0.64 3.23 1.692 4.39l0.072 0.069c0.131 0.111 0.3 0.179 0.486 0.179 0.414 0 0.75-0.337 0.75-0.753 0-0.173-0.058-0.332-0.156-0.46l-0.2-0.23C3.93 14.363 3.5 13.249 3.5 12.035c0-2.771 2.239-5.018 5-5.018h6.881l-1.832 1.84-0.067 0.078C13.26 9.23 13.281 9.653 13.55 9.922c0.292 0.294 0.767 0.294 1.06 0l3.182-3.194 0.067-0.077c0.224-0.295 0.202-0.717-0.067-0.987L14.61 2.471zm5.62 5.101c-0.13-0.109-0.297-0.174-0.48-0.174-0.414 0-0.75 0.337-0.75 0.752 0 0.187 0.068 0.358 0.18 0.488 0.82 0.894 1.32 2.088 1.32 3.398 0 2.772-2.239 5.019-5 5.019H8.558l1.905-1.911 0.074-0.086c0.197-0.267 0.195-0.636-0.007-0.902l-0.067-0.077-0.084-0.073c-0.267-0.199-0.635-0.197-0.9 0.006l-0.076 0.067-3.182 3.194-0.073 0.085c-0.198 0.267-0.196 0.636 0.006 0.902l0.067 0.077 3.182 3.194 0.084 0.072c0.293 0.22 0.71 0.195 0.976-0.073 0.269-0.269 0.291-0.692 0.068-0.987l-0.068-0.077-1.899-1.906H15.5l0.233-0.004C19.215 18.432 22 15.56 22 12.035c0-1.693-0.643-3.236-1.697-4.395L20.23 7.57z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M9.562 3c-4.143 0-7.5 3.358-7.5 7.5 0 1.133 0.251 2.209 0.702 3.173L2.04 16.515c-0.233 0.913 0.59 1.744 1.504 1.524 0.75-0.18 1.903-0.457 2.93-0.702C7.417 17.763 8.463 18 9.563 18c4.142 0 7.5-3.358 7.5-7.5 0-4.142-3.358-7.5-7.5-7.5zm-6 7.5c0-3.314 2.686-6 6-6 3.313 0 6 2.686 6 6s-2.687 6-6 6c-0.961 0-1.867-0.225-2.67-0.625l-0.244-0.121-0.264 0.063c-0.923 0.22-1.99 0.475-2.788 0.667l0.69-2.708 0.07-0.276-0.13-0.253C3.8 12.425 3.562 11.49 3.562 10.5zm11 10.5c-1.97 0-3.762-0.759-5.1-2h0.1c0.718 0 1.415-0.089 2.08-0.257 0.865 0.482 1.86 0.757 2.92 0.757 0.96 0 1.866-0.225 2.67-0.625l0.243-0.121 0.264 0.063c0.922 0.22 1.966 0.445 2.74 0.61-0.175-0.751-0.414-1.756-0.642-2.651l-0.07-0.276 0.13-0.253c0.425-0.822 0.665-1.755 0.665-2.747 0-2.115-1.094-3.974-2.747-5.042-0.179-0.724-0.45-1.41-0.8-2.048 2.937 1.017 5.047 3.807 5.047 7.09 0 1.133-0.252 2.21-0.703 3.174 0.253 1.008 0.509 2.1 0.671 2.803 0.205 0.885-0.575 1.686-1.467 1.5-0.727-0.152-1.87-0.396-2.913-0.64C16.706 20.763 15.66 21 14.561 21z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M6.747 4h3.464c0.414 0 0.75 0.335 0.75 0.75 0 0.38-0.282 0.693-0.648 0.743L10.21 5.499H6.747c-1.191 0-2.166 0.926-2.245 2.097L4.497 7.75v9.5c0 1.19 0.925 2.165 2.096 2.244L6.747 19.5h9.5c1.191 0 2.166-0.926 2.245-2.096l0.005-0.154v-0.498c0-0.415 0.336-0.75 0.75-0.75 0.38 0 0.694 0.282 0.744 0.648l0.006 0.102v0.498c0 2.004-1.572 3.64-3.55 3.744L16.247 21h-9.5c-2.005 0-3.642-1.573-3.745-3.551l-0.005-0.2v-9.5c0-2.004 1.572-3.64 3.55-3.744l0.2-0.006h3.464-3.464zM14.5 6.52V3.75c0-0.624 0.706-0.96 1.187-0.61l0.082 0.068 5.994 5.75c0.28 0.269 0.306 0.7 0.077 0.998l-0.077 0.085-5.994 5.752c-0.45 0.432-1.182 0.154-1.262-0.435L14.5 15.251v-2.725l-0.344 0.03c-2.4 0.25-4.7 1.331-6.915 3.26-0.519 0.453-1.322 0.025-1.236-0.658C6.67 9.838 9.452 6.907 14.2 6.54l0.3-0.02V3.75v2.77zM16 5.507V7.25C16 7.663 15.665 8 15.25 8c-3.874 0-6.274 1.676-7.312 5.157L7.86 13.435l0.352-0.237C10.45 11.737 12.798 11 15.251 11c0.38 0 0.693 0.283 0.743 0.649l0.006 0.1v1.743L20.16 9.5 16 5.508z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M10.788 3.102c0.495-1.003 1.926-1.003 2.421 0l2.358 4.778 5.273 0.766c1.107 0.16 1.549 1.522 0.748 2.303l-3.816 3.719 0.901 5.25c0.19 1.104-0.968 1.945-1.959 1.424l-4.716-2.48-4.715 2.48c-0.99 0.52-2.148-0.32-1.96-1.423l0.901-5.251-3.815-3.72c-0.801-0.78-0.359-2.141 0.748-2.302L8.43 7.88l2.358-4.778z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M10.788 3.102c0.495-1.003 1.926-1.003 2.421 0l2.358 4.778 5.273 0.766c1.107 0.16 1.549 1.522 0.748 2.303l-3.816 3.719 0.901 5.25c0.19 1.104-0.968 1.945-1.959 1.424l-4.716-2.48-4.715 2.48c-0.99 0.52-2.148-0.32-1.96-1.423l0.901-5.251-3.815-3.72c-0.801-0.78-0.359-2.141 0.748-2.302L8.43 7.88l2.358-4.778zm1.21 0.937L9.74 8.614C9.543 9.013 9.163 9.29 8.724 9.353l-5.05 0.734 3.654 3.562c0.318 0.31 0.463 0.757 0.388 1.195l-0.862 5.029 4.516-2.375c0.394-0.207 0.863-0.207 1.257 0l4.516 2.375-0.862-5.03c-0.075-0.438 0.07-0.884 0.388-1.194l3.654-3.562-5.05-0.734c-0.44-0.064-0.82-0.34-1.016-0.739l-2.259-4.576z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="4dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="4">
|
||||||
|
<path
|
||||||
|
android:pathData="M4,2C4,3.1046 3.1046,4 2,4C0.8954,4 0,3.1046 0,2C0,0.8954 0.8954,0 2,0C3.1046,0 4,0.8954 4,2ZM10,2C10,3.1046 9.1046,4 8,4C6.8954,4 6,3.1046 6,2C6,0.8954 6.8954,0 8,0C9.1046,0 10,0.8954 10,2ZM14,4C15.1046,4 16,3.1046 16,2C16,0.8954 15.1046,0 14,0C12.8954,0 12,0.8954 12,2C12,3.1046 12.8954,4 14,4Z"
|
||||||
|
android:fillColor="@color/gray_500"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingRight="20dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:minWidth="56dp">
|
||||||
|
<CheckedTextView
|
||||||
|
android:id="@+id/reply"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:drawableStart="@drawable/ic_fluent_chat_multiple_24_regular"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:drawableTint="@color/text_secondary"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
tools:text="123"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0px"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:minWidth="56dp">
|
||||||
|
<CheckedTextView
|
||||||
|
android:id="@+id/boost"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:drawableStart="@drawable/ic_fluent_arrow_repeat_all_24_regular"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:drawableTint="@color/text_secondary"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
tools:text="123"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0px"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:minWidth="56dp">
|
||||||
|
<CheckedTextView
|
||||||
|
android:id="@+id/favorite"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:drawableStart="@drawable/ic_fluent_star_24_regular"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:drawableTint="@color/text_secondary"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_label_large"
|
||||||
|
tools:text="123"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0px"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:minWidth="56dp">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/share"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:src="@drawable/ic_fluent_share_24_regular"
|
||||||
|
android:tint="@color/text_secondary"
|
||||||
|
android:gravity="center_vertical"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -3,32 +3,73 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingTop="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingLeft="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/more"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:background="?android:selectableItemBackgroundBorderless"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_post_more"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/avatar"
|
android:id="@+id/avatar"
|
||||||
android:layout_width="48dp"
|
android:layout_width="46dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="46dp"
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentTop="true"/>
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginEnd="12dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/name"
|
android:id="@+id/name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="24dp"
|
||||||
android:layout_toEndOf="@id/avatar"
|
android:layout_toEndOf="@id/avatar"
|
||||||
android:textStyle="bold"
|
android:layout_toStartOf="@id/more"
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
tools:text="Eugen"/>
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/m3_title_medium"
|
||||||
|
tools:text="Eugen" />
|
||||||
|
|
||||||
<TextView
|
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||||
android:id="@+id/subtitle"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="20dp"
|
||||||
|
android:layout_below="@id/name"
|
||||||
android:layout_toEndOf="@id/avatar"
|
android:layout_toEndOf="@id/avatar"
|
||||||
android:layout_alignBottom="@id/avatar"
|
android:layoutDirection="locale"
|
||||||
tools:text="\@Gargron . 1d"/>
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/m3_title_small"
|
||||||
|
tools:text="\@Gargron" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/separator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:text="·"
|
||||||
|
android:textAppearance="@style/m3_title_small" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/timestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:textAppearance="@style/m3_title_small"
|
||||||
|
android:singleLine="true"
|
||||||
|
tools:text="3h" />
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
|
@ -2,12 +2,19 @@
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:layout_marginBottom="-6dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/m3_title_small"
|
||||||
|
android:drawableStart="@drawable/ic_fluent_arrow_repeat_all_20_filled"
|
||||||
|
android:drawableTint="@color/gray_500"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:ellipsize="end"/>
|
android:ellipsize="end"/>
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
<org.joinmastodon.android.ui.views.LinkedTextView
|
<org.joinmastodon.android.ui.views.LinkedTextView
|
||||||
android:id="@+id/text"
|
android:id="@+id/text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/m3_body_large"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingLeft="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="46dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_marginEnd="12dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_toEndOf="@id/avatar"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/m3_title_medium"
|
||||||
|
tools:text="Eugen" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_below="@id/name"
|
||||||
|
android:layout_toEndOf="@id/avatar"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/m3_title_small"
|
||||||
|
tools:text="\@Gargron" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/toot_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0px"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:textAppearance="@style/m3_body_large"
|
||||||
|
android:gravity="top"
|
||||||
|
android:background="@null"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/char_counter"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="500"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -1,28 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/toot_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0px"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="top"
|
|
||||||
android:inputType="textMultiLine|textCapSentences"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="8dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/char_counter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<me.grishka.appkit.views.RecursiveSwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/refresh_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/content_wrap"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<me.grishka.appkit.views.UsableRecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:clipToPadding="false"/>
|
||||||
|
|
||||||
|
<ViewStub android:layout="?emptyViewLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/empty"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:background="@drawable/bg_fab"
|
||||||
|
android:tint="@color/base"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:stateListAnimator="@animator/fab_shadow"
|
||||||
|
android:src="@drawable/ic_edit_34"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
</me.grishka.appkit.views.RecursiveSwipeRefreshLayout>
|
|
@ -7,4 +7,15 @@
|
||||||
<color name="teal_700">#FF018786</color>
|
<color name="teal_700">#FF018786</color>
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
|
||||||
|
<color name="fluent_default_icon_tint">@color/gray_800</color>
|
||||||
|
|
||||||
|
<color name="gray_800">#282C37</color>
|
||||||
|
<color name="gray_500">#667085</color>
|
||||||
|
<color name="gray_50t">#CCF9FAFB</color>
|
||||||
|
|
||||||
|
<color name="text_primary">@color/gray_800</color>
|
||||||
|
<color name="text_secondary">@color/gray_500</color>
|
||||||
|
<color name="secondary">#E9EDF2</color>
|
||||||
|
<color name="base">#282C37</color>
|
||||||
</resources>
|
</resources>
|
|
@ -20,4 +20,9 @@
|
||||||
<string name="user_favorited">%s favorited your toot</string>
|
<string name="user_favorited">%s favorited your toot</string>
|
||||||
<string name="poll_ended">Poll you voted in has ended</string>
|
<string name="poll_ended">Poll you voted in has ended</string>
|
||||||
<string name="user_posted">%s posted</string>
|
<string name="user_posted">%s posted</string>
|
||||||
|
|
||||||
|
<string name="time_seconds">%ds</string>
|
||||||
|
<string name="time_minutes">%dm</string>
|
||||||
|
<string name="time_hours">%dh</string>
|
||||||
|
<string name="time_days">%dd</string>
|
||||||
</resources>
|
</resources>
|
|
@ -4,5 +4,30 @@
|
||||||
<!-- needed to disable scrim on API 29+ -->
|
<!-- needed to disable scrim on API 29+ -->
|
||||||
<item name="android:enforceNavigationBarContrast">false</item>
|
<item name="android:enforceNavigationBarContrast">false</item>
|
||||||
<item name="android:enforceStatusBarContrast">false</item>
|
<item name="android:enforceStatusBarContrast">false</item>
|
||||||
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
<item name="android:windowBackground">@color/white</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="m3_body_large">
|
||||||
|
<item name="android:textSize">16dp</item>
|
||||||
|
<item name="android:textColor">@color/text_primary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="m3_title_medium">
|
||||||
|
<item name="android:fontFamily">sans-serif-medium</item>
|
||||||
|
<item name="android:textSize">16dp</item>
|
||||||
|
<item name="android:textColor">@color/text_primary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="m3_title_small">
|
||||||
|
<item name="android:fontFamily">sans-serif-medium</item>
|
||||||
|
<item name="android:textSize">14dp</item>
|
||||||
|
<item name="android:textColor">@color/text_secondary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="m3_label_large">
|
||||||
|
<item name="android:fontFamily">sans-serif-medium</item>
|
||||||
|
<item name="android:textColor">@color/text_secondary</item>
|
||||||
|
<item name="android:textSize">14dp</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue