Compare commits

...

2 commits

Author SHA1 Message Date
instant992
6f2747c926 Add Space/Cloud themes, WiFi 3 for all, Gitea OTA and source URLs 2026-06-23 01:31:53 +04:00
instant992
b180b7bc30 Switch OTA and source code URLs to git.vpnghost.space 2026-06-23 01:30:49 +04:00
94 changed files with 3021 additions and 367 deletions

View file

@ -148,7 +148,7 @@ android {
arguments '-DANDROID_STL=c++_static', '-DANDROID_PLATFORM=android-23', '-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON', '-DCMAKE_BUILD_TYPE=Release' arguments '-DANDROID_STL=c++_static', '-DANDROID_PLATFORM=android-23', '-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON', '-DCMAKE_BUILD_TYPE=Release'
abiFilters "arm64-v8a" abiFilters "arm64-v8a"
System.getenv("PATH").split(File.pathSeparator).any { path -> System.getenv("PATH").split(File.pathSeparator).any { path ->
var file = Paths.get("${path}${File.separator}ccache${if (OperatingSystem.current().windows) ".exe" else ""}").toFile() var file = Paths.get("${path.trim()}${File.separator}ccache${if (OperatingSystem.current().windows) ".exe" else ""}").toFile()
if (file.exists()) { if (file.exists()) {
println("Using ccache ${file.getAbsolutePath()}") println("Using ccache ${file.getAbsolutePath()}")
arguments += "-DANDROID_CCACHE=${file.getAbsolutePath()}" arguments += "-DANDROID_CCACHE=${file.getAbsolutePath()}"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -265,7 +265,11 @@ public class AndroidUtilities {
public static Typeface bold() { public static Typeface bold() {
if (mediumTypeface == null) { if (mediumTypeface == null) {
if (SharedConfig.useSystemBoldFont && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Check if a custom font is selected via FontHelper
Typeface customTypeface = tw.nekomimi.nekogram.helpers.FontHelper.getMediumTypeface();
if (customTypeface != null) {
mediumTypeface = customTypeface;
} else if (SharedConfig.useSystemBoldFont && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
mediumTypeface = Typeface.create(null, 500, false); mediumTypeface = Typeface.create(null, 500, false);
} else { } else {
mediumTypeface = getTypeface(TYPEFACE_ROBOTO_MEDIUM); mediumTypeface = getTypeface(TYPEFACE_ROBOTO_MEDIUM);

View file

@ -254,6 +254,7 @@ public class ApplicationLoader extends Application {
} }
SharedConfig.loadConfig(); SharedConfig.loadConfig();
tw.nekomimi.nekogram.helpers.FontHelper.init();
SharedPrefsHelper.init(applicationContext); SharedPrefsHelper.init(applicationContext);
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { //TODO improve account for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { //TODO improve account
UserConfig.getInstance(a).loadConfig(); UserConfig.getInstance(a).loadConfig();

View file

@ -401,6 +401,8 @@ public class SharedConfig {
public boolean builtin = false; public boolean builtin = false;
/** Display name for a built-in proxy (e.g. "LTE 1", "WiFi 2"). */ /** Display name for a built-in proxy (e.g. "LTE 1", "WiFi 2"). */
public String builtinName = ""; public String builtinName = "";
/** True if this server is only available to sponsors. */
public boolean sponsorOnly = false;
public int vlessLocalPort = 10808; public int vlessLocalPort = 10808;
public long proxyCheckPingId; public long proxyCheckPingId;
@ -1600,6 +1602,33 @@ public class SharedConfig {
} }
} }
/**
* Reload built-in proxies after a remote refresh.
* Removes old built-in entries and re-inserts from the updated cache.
* Should be called on the UI thread (or with subsequent UI update).
*/
public static void reloadBuiltinProxies() {
// Remove old built-in entries
for (java.util.Iterator<ProxyInfo> it = proxyList.iterator(); it.hasNext(); ) {
if (it.next().builtin) it.remove();
}
// Re-insert updated built-ins at the top
java.util.List<ProxyInfo> builtins = XrayController.createBuiltinProxies();
for (int i = builtins.size() - 1; i >= 0; i--) {
proxyList.add(0, builtins.get(i));
}
// If current proxy was built-in, find its replacement by localPort
if (currentProxy != null && currentProxy.builtin) {
int port = currentProxy.vlessLocalPort;
ProxyInfo replacement = null;
for (ProxyInfo p : proxyList) {
if (p.builtin && p.vlessLocalPort == port) { replacement = p; break; }
}
if (replacement == null && !proxyList.isEmpty()) replacement = proxyList.get(0);
currentProxy = replacement;
}
}
public static void saveProxyList() { public static void saveProxyList() {
// Exclude builtin proxy from serialization it's always injected at load time // Exclude builtin proxy from serialization it's always injected at load time
List<ProxyInfo> infoToSerialize = new ArrayList<>(); List<ProxyInfo> infoToSerialize = new ArrayList<>();

View file

@ -11,13 +11,20 @@ import android.net.NetworkRequest;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.ConnectionsManager;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/** /**
* Controls the embedded Xray (VLESS+Reality) instance. * Controls the embedded Xray (VLESS+Reality) instance.
@ -35,7 +42,7 @@ public class XrayController {
/** Build all configured built-in proxies, in declaration order. */ /** Build all configured built-in proxies, in declaration order. */
public static java.util.List<SharedConfig.ProxyInfo> createBuiltinProxies() { public static java.util.List<SharedConfig.ProxyInfo> createBuiltinProxies() {
java.util.List<SharedConfig.ProxyInfo> list = new java.util.ArrayList<>(); java.util.List<SharedConfig.ProxyInfo> list = new java.util.ArrayList<>();
for (XrayServers.Server s : XrayServers.SERVERS) { for (XrayServers.Server s : allEffectiveServers()) {
SharedConfig.ProxyInfo p = fromServer(s); SharedConfig.ProxyInfo p = fromServer(s);
if (p != null) list.add(p); if (p != null) list.add(p);
} }
@ -44,7 +51,7 @@ public class XrayController {
/** Build the built-in proxy assigned to the given local SOCKS5 port. */ /** Build the built-in proxy assigned to the given local SOCKS5 port. */
public static SharedConfig.ProxyInfo createBuiltinProxyByPort(int localPort) { public static SharedConfig.ProxyInfo createBuiltinProxyByPort(int localPort) {
for (XrayServers.Server s : XrayServers.SERVERS) { for (XrayServers.Server s : allEffectiveServers()) {
if (s.localPort == localPort) { if (s.localPort == localPort) {
return fromServer(s); return fromServer(s);
} }
@ -54,7 +61,7 @@ public class XrayController {
/** Build the first configured built-in proxy, or null if none. */ /** Build the first configured built-in proxy, or null if none. */
public static SharedConfig.ProxyInfo createBuiltinProxy() { public static SharedConfig.ProxyInfo createBuiltinProxy() {
for (XrayServers.Server s : XrayServers.SERVERS) { for (XrayServers.Server s : allEffectiveServers()) {
SharedConfig.ProxyInfo p = fromServer(s); SharedConfig.ProxyInfo p = fromServer(s);
if (p != null) return p; if (p != null) return p;
} }
@ -89,7 +96,7 @@ public class XrayController {
/** First built-in proxy that matches the current network, or null. */ /** First built-in proxy that matches the current network, or null. */
public static SharedConfig.ProxyInfo createBuiltinProxyForCurrentNetwork() { public static SharedConfig.ProxyInfo createBuiltinProxyForCurrentNetwork() {
SharedConfig.ProxyInfo fallback = null; SharedConfig.ProxyInfo fallback = null;
for (XrayServers.Server s : XrayServers.SERVERS) { for (XrayServers.Server s : allEffectiveServers()) {
SharedConfig.ProxyInfo p = fromServer(s); SharedConfig.ProxyInfo p = fromServer(s);
if (p == null) continue; if (p == null) continue;
if (fallback == null) fallback = p; if (fallback == null) fallback = p;
@ -121,7 +128,7 @@ public class XrayController {
/** All built-in proxies that match the current network, in declaration order. */ /** All built-in proxies that match the current network, in declaration order. */
public static java.util.List<SharedConfig.ProxyInfo> builtinProxiesForCurrentNetwork() { public static java.util.List<SharedConfig.ProxyInfo> builtinProxiesForCurrentNetwork() {
java.util.List<SharedConfig.ProxyInfo> list = new java.util.ArrayList<>(); java.util.List<SharedConfig.ProxyInfo> list = new java.util.ArrayList<>();
for (XrayServers.Server s : XrayServers.SERVERS) { for (XrayServers.Server s : allEffectiveServers()) {
SharedConfig.ProxyInfo p = fromServer(s); SharedConfig.ProxyInfo p = fromServer(s);
if (p != null && matchesCurrentNetwork(p)) list.add(p); if (p != null && matchesCurrentNetwork(p)) list.add(p);
} }
@ -157,6 +164,7 @@ public class XrayController {
s.network, s.serviceName); s.network, s.serviceName);
p.builtin = true; p.builtin = true;
p.builtinName = s.name != null ? s.name : ""; p.builtinName = s.name != null ? s.name : "";
p.sponsorOnly = s.sponsorOnly;
return p; return p;
} }
@ -173,6 +181,147 @@ public class XrayController {
} }
} }
// Dynamic server list
private static final String PREFS_SERVERS = "xray_servers";
private static final String PREFS_KEY_LTE = "cached_lte";
private static final String PREFS_KEY_WIFI = "cached_wifi";
private static final long CACHE_TTL_MS = 24 * 60 * 60 * 1000L; // 24h
/** Cached dynamic server list; null means "not yet loaded or fallback mode". */
private static volatile List<XrayServers.Server> sCachedLte = null;
private static volatile List<XrayServers.Server> sCachedWifi = null;
/**
* Returns the effective server list for the given category.
* If a dynamic list has been loaded it is returned; otherwise falls back
* to the hard-coded {@link XrayServers#SERVERS} array.
*/
private static List<XrayServers.Server> effectiveServers(String category) {
List<XrayServers.Server> dyn = "lte".equals(category) ? sCachedLte : sCachedWifi;
if (dyn != null && !dyn.isEmpty()) return dyn;
// fallback: filter built-in list by category
List<XrayServers.Server> list = new ArrayList<>();
for (XrayServers.Server s : XrayServers.SERVERS) {
String name = s.name != null ? s.name.toLowerCase(java.util.Locale.ROOT) : "";
boolean isWifi = name.contains("wifi") || name.contains("wi-fi");
boolean isLte = name.contains("lte") || name.contains("mobile");
if ("wifi".equals(category) ? isWifi : isLte) list.add(s);
}
if (list.isEmpty()) {
// if no match, return everything
for (XrayServers.Server s : XrayServers.SERVERS) list.add(s);
}
return list;
}
/** All effective servers (both categories), same order as API returns. */
private static List<XrayServers.Server> allEffectiveServers() {
if (sCachedLte != null && sCachedWifi != null
&& (!sCachedLte.isEmpty() || !sCachedWifi.isEmpty())) {
List<XrayServers.Server> all = new ArrayList<>(sCachedLte);
all.addAll(sCachedWifi);
return all;
}
List<XrayServers.Server> list = new ArrayList<>();
for (XrayServers.Server s : XrayServers.SERVERS) list.add(s);
return list;
}
/**
* Fetch the server list from the FoxCloud API on a background thread.
* On success updates {@link #sCachedLte} / {@link #sCachedWifi} and
* (optionally) calls {@code onDone} on the same background thread.
* Falls back silently to hard-coded servers on any error.
*
* @param ctx application context (may be null skips cache persist)
* @param onDone callback invoked when done (success or failure); may be null
*/
public static void refreshServers(Context ctx, Runnable onDone) {
new Thread(() -> {
boolean ok = doFetchServers(ctx);
Log.d(TAG, "refreshServers: " + (ok ? "success" : "failed — using built-in fallback"));
if (onDone != null) onDone.run();
}, "xray-refresh-servers").start();
}
private static boolean doFetchServers(Context ctx) {
try {
String apiUrl = XrayServers.API_BASE_URL + "/api/servers?token="
+ XrayServers.API_TOKEN;
URL url = new URL(apiUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10_000);
conn.setReadTimeout(10_000);
conn.setRequestProperty("Accept", "application/json");
// Pass Telegram ID so the backend can return sponsor-only servers
try {
org.telegram.messenger.UserConfig uc =
org.telegram.messenger.UserConfig.getInstance(
org.telegram.messenger.UserConfig.selectedAccount);
if (uc != null && uc.getCurrentUser() != null) {
conn.setRequestProperty("X-Tg-Id", String.valueOf(uc.getCurrentUser().id));
}
} catch (Exception ignored) {}
int code = conn.getResponseCode();
if (code != 200) {
Log.w(TAG, "doFetchServers: HTTP " + code);
return false;
}
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) sb.append(line);
}
JSONObject root = new JSONObject(sb.toString());
if (!root.optBoolean("success", false)) return false;
sCachedLte = parseServerArray(root.optJSONArray("lte"), "lte");
sCachedWifi = parseServerArray(root.optJSONArray("wifi"), "wifi");
Log.d(TAG, "doFetchServers: lte=" + sCachedLte.size()
+ " wifi=" + sCachedWifi.size());
return true;
} catch (Exception e) {
Log.e(TAG, "doFetchServers exception", e);
return false;
}
}
private static List<XrayServers.Server> parseServerArray(JSONArray arr, String defaultCat) {
List<XrayServers.Server> list = new ArrayList<>();
if (arr == null) return list;
for (int i = 0; i < arr.length(); i++) {
try {
JSONObject o = arr.getJSONObject(i);
String name = o.optString("name", defaultCat.toUpperCase() + " " + (i + 1));
String address = o.optString("address", "");
int port = o.optInt ("port", 443);
String uuid = o.optString("uuid", "");
String publicKey = o.optString("publicKey", "");
String shortId = o.optString("shortId", "");
String fingerprint = o.optString("fingerprint", "chrome");
String sni = o.optString("sni", address);
String network = o.optString("network", "tcp");
String serviceName = o.optString("serviceName", "");
int localPort = o.optInt ("localPort", 10808 + i);
boolean sponsorOnly = o.optBoolean("sponsorOnly", false);
if (address.isEmpty() || uuid.isEmpty()) continue;
list.add(new XrayServers.Server(name, address, port, uuid,
publicKey, shortId, fingerprint, sni,
network, serviceName, localPort, sponsorOnly));
} catch (Exception e) {
Log.w(TAG, "parseServerArray: skip entry " + i, e);
}
}
return list;
}
//
public static void init(Context ctx) { public static void init(Context ctx) {
sAppContext = ctx.getApplicationContext(); sAppContext = ctx.getApplicationContext();
} }

View file

@ -263,7 +263,7 @@ public class ActionBarMenuItem extends FrameLayout {
iconView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); iconView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
addView(iconView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); addView(iconView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
if (iconColor != 0) { if (iconColor != 0) {
iconView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.MULTIPLY)); iconView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN));
} }
} }
} }
@ -380,13 +380,13 @@ public class ActionBarMenuItem extends FrameLayout {
public void setIconColor(int color) { public void setIconColor(int color) {
if (iconView != null) { if (iconView != null) {
iconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); iconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
if (textView != null) { if (textView != null) {
textView.setTextColor(color); textView.setTextColor(color);
} }
if (clearButton != null) { if (clearButton != null) {
clearButton.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); clearButton.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} }

View file

@ -82,7 +82,7 @@ public class ActionBarMenuSubItem extends FrameLayout {
textColor = getThemedColor(Theme.key_actionBarDefaultSubmenuItem); textColor = getThemedColor(Theme.key_actionBarDefaultSubmenuItem);
iconColor = getThemedColor(Theme.key_actionBarDefaultSubmenuItemIcon); iconColor = getThemedColor(Theme.key_actionBarDefaultSubmenuItemIcon);
iconColorMode = PorterDuff.Mode.MULTIPLY; iconColorMode = PorterDuff.Mode.SRC_IN;
selectorColor = getThemedColor(Theme.key_dialogButtonSelector); selectorColor = getThemedColor(Theme.key_dialogButtonSelector);
updateBackground(); updateBackground();
@ -90,7 +90,7 @@ public class ActionBarMenuSubItem extends FrameLayout {
imageView = new RLottieImageView(context); imageView = new RLottieImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.MULTIPLY)); imageView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN));
addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 40, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 40, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)));
textView = new AnimatedEmojiSpan.TextViewEmojis(context); textView = new AnimatedEmojiSpan.TextViewEmojis(context);
@ -169,7 +169,7 @@ public class ActionBarMenuSubItem extends FrameLayout {
if (rightIcon == null) { if (rightIcon == null) {
rightIcon = new ImageView(getContext()); rightIcon = new ImageView(getContext());
rightIcon.setScaleType(ImageView.ScaleType.CENTER); rightIcon.setScaleType(ImageView.ScaleType.CENTER);
rightIcon.setColorFilter(iconColor, PorterDuff.Mode.MULTIPLY); rightIcon.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
if (LocaleController.isRTL) { if (LocaleController.isRTL) {
rightIcon.setScaleX(-1); rightIcon.setScaleX(-1);
} }
@ -275,7 +275,7 @@ public class ActionBarMenuSubItem extends FrameLayout {
} }
public void setIconColor(int iconColor) { public void setIconColor(int iconColor) {
setIconColor(iconColor, PorterDuff.Mode.MULTIPLY); setIconColor(iconColor, PorterDuff.Mode.SRC_IN);
} }
public void setIconColor(int iconColor, PorterDuff.Mode mode) { public void setIconColor(int iconColor, PorterDuff.Mode mode) {

View file

@ -250,7 +250,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati
imageView = new ImageView(context); imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_dialogIcon), PorterDuff.Mode.MULTIPLY)); imageView.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_dialogIcon), PorterDuff.Mode.SRC_IN));
addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 40, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 40, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)));
textView = new TextView(context); textView = new TextView(context);
@ -1607,7 +1607,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati
} }
AlertDialogCell cell = itemViews.get(item); AlertDialogCell cell = itemViews.get(item);
cell.textView.setTextColor(color); cell.textView.setTextColor(color);
cell.imageView.setColorFilter(new PorterDuffColorFilter(icon, PorterDuff.Mode.MULTIPLY)); cell.imageView.setColorFilter(new PorterDuffColorFilter(icon, PorterDuff.Mode.SRC_IN));
} }
public int getItemsCount() { public int getItemsCount() {

View file

@ -1040,7 +1040,7 @@ public class BottomSheet extends Dialog implements BaseFragment.AttachedSheet {
imageView = new ImageView(context); imageView = new ImageView(context);
imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_dialogIcon), PorterDuff.Mode.MULTIPLY)); imageView.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_dialogIcon), PorterDuff.Mode.SRC_IN));
addView(imageView, LayoutHelper.createFrame(56, 48, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); addView(imageView, LayoutHelper.createFrame(56, 48, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)));
imageView2 = new ImageView(context); imageView2 = new ImageView(context);
@ -1087,7 +1087,7 @@ public class BottomSheet extends Dialog implements BaseFragment.AttachedSheet {
} }
public void setIconColor(int color) { public void setIconColor(int color) {
imageView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); imageView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
public void setGravity(int gravity) { public void setGravity(int gravity) {
@ -1834,7 +1834,7 @@ public class BottomSheet extends Dialog implements BaseFragment.AttachedSheet {
} }
BottomSheetCell cell = itemViews.get(item); BottomSheetCell cell = itemViews.get(item);
cell.textView.setTextColor(color); cell.textView.setTextColor(color);
cell.imageView.setColorFilter(new PorterDuffColorFilter(icon, PorterDuff.Mode.MULTIPLY)); cell.imageView.setColorFilter(new PorterDuffColorFilter(icon, PorterDuff.Mode.SRC_IN));
} }
public ArrayList<BottomSheetCell> getItemViews() { public ArrayList<BottomSheetCell> getItemViews() {

View file

@ -85,6 +85,7 @@ public class SimpleTextView extends View implements Drawable.Callback {
private float scrollingOffset; private float scrollingOffset;
private long lastUpdateTime; private long lastUpdateTime;
private int currentScrollDelay; private int currentScrollDelay;
private int scrollDelayMs = SCROLL_DELAY_MS;
private Paint fadePaint; private Paint fadePaint;
private Paint fadePaintBack; private Paint fadePaintBack;
private Paint fadeEllpsizePaint; private Paint fadeEllpsizePaint;
@ -233,6 +234,14 @@ public class SimpleTextView extends View implements Drawable.Callback {
checkUi_layerType(); checkUi_layerType();
} }
/**
* Pause (in ms) the marquee holds at the start of the text before it begins scrolling,
* and again after each full loop. Defaults to {@link #SCROLL_DELAY_MS}.
*/
public void setScrollDelayMs(int value) {
scrollDelayMs = value;
}
public void setEllipsizeByGradient(boolean value) { public void setEllipsizeByGradient(boolean value) {
setEllipsizeByGradient(value, null); setEllipsizeByGradient(value, null);
} }
@ -338,8 +347,27 @@ public class SimpleTextView extends View implements Drawable.Callback {
textHeight = layout.getLineBottom(0); textHeight = layout.getLineBottom(0);
} }
int rightDrawableWidthForFit = 0;
if (rightDrawableInside) {
if (rightDrawable != null && !rightDrawableOutside) {
rightDrawableWidthForFit += (int) (rightDrawable.getIntrinsicWidth() * rightDrawableScale);
}
if (rightDrawable2 != null && !rightDrawableOutside) {
rightDrawableWidthForFit += (int) (rightDrawable2.getIntrinsicWidth() * rightDrawableScale);
}
}
// Same overflow test that drives the marquee (see textDoesNotFit below).
final boolean overflowsForScroll = textWidth + rightDrawableWidthForFit > (width - paddingRight);
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.CENTER_HORIZONTAL) { if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.CENTER_HORIZONTAL) {
// When the text fits, center it. When it overflows and marquee scrolling is on,
// start from the left edge so the scroll animation reveals the text from its
// beginning instead of starting already shifted by a few pixels/characters.
if (scrollNonFitText && overflowsForScroll) {
offsetX = -(int) layout.getLineLeft(0);
} else {
offsetX = (width - textWidth) / 2 - (int) layout.getLineLeft(0); offsetX = (width - textWidth) / 2 - (int) layout.getLineLeft(0);
}
} else if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT) { } else if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT) {
if (firstLineLayout != null) { if (firstLineLayout != null) {
offsetX = -(int) firstLineLayout.getLineLeft(0); offsetX = -(int) firstLineLayout.getLineLeft(0);
@ -356,16 +384,7 @@ public class SimpleTextView extends View implements Drawable.Callback {
offsetX = -dp(8); offsetX = -dp(8);
} }
offsetX += getPaddingLeft(); offsetX += getPaddingLeft();
int rightDrawableWidth = 0; textDoesNotFit = overflowsForScroll;
if (rightDrawableInside) {
if (rightDrawable != null && !rightDrawableOutside) {
rightDrawableWidth += (int) (rightDrawable.getIntrinsicWidth() * rightDrawableScale);
}
if (rightDrawable2 != null && !rightDrawableOutside) {
rightDrawableWidth += (int) (rightDrawable2.getIntrinsicWidth() * rightDrawableScale);
}
}
textDoesNotFit = textWidth + rightDrawableWidth > (width - paddingRight);
checkUi_layerType(); checkUi_layerType();
if (fullLayout != null && fullLayoutAdditionalWidth > 0) { if (fullLayout != null && fullLayoutAdditionalWidth > 0) {
@ -511,7 +530,7 @@ public class SimpleTextView extends View implements Drawable.Callback {
if (lastWidth != AndroidUtilities.displaySize.x) { if (lastWidth != AndroidUtilities.displaySize.x) {
lastWidth = AndroidUtilities.displaySize.x; lastWidth = AndroidUtilities.displaySize.x;
scrollingOffset = 0; scrollingOffset = 0;
currentScrollDelay = SCROLL_DELAY_MS; currentScrollDelay = scrollDelayMs;
checkUi_layerType(); checkUi_layerType();
} }
createLayout(width - getPaddingLeft() - getPaddingRight() - minusWidth - (leftDrawableOutside && leftDrawable != null ? leftDrawable.getIntrinsicWidth() + drawablePadding : 0) - (rightDrawableOutside && rightDrawable != null ? rightDrawable.getIntrinsicWidth() + drawablePadding : 0) - (rightDrawableOutside && rightDrawable2 != null ? rightDrawable2.getIntrinsicWidth() + drawablePadding : 0)); createLayout(width - getPaddingLeft() - getPaddingRight() - minusWidth - (leftDrawableOutside && leftDrawable != null ? leftDrawable.getIntrinsicWidth() + drawablePadding : 0) - (rightDrawableOutside && rightDrawable != null ? rightDrawable.getIntrinsicWidth() + drawablePadding : 0) - (rightDrawableOutside && rightDrawable2 != null ? rightDrawable2.getIntrinsicWidth() + drawablePadding : 0));
@ -703,7 +722,7 @@ public class SimpleTextView extends View implements Drawable.Callback {
return false; return false;
} }
text = value; text = value;
currentScrollDelay = SCROLL_DELAY_MS; currentScrollDelay = scrollDelayMs;
recreateLayoutMaybe(); recreateLayoutMaybe();
return true; return true;
} }
@ -1125,6 +1144,12 @@ public class SimpleTextView extends View implements Drawable.Callback {
} }
if (rightDrawable != null && rightDrawableOutside) { if (rightDrawable != null && rightDrawableOutside) {
int x = Math.min(textOffsetX + textWidth + drawablePadding + (scrollingOffset == 0 ? -nextScrollX : (int) -scrollingOffset) + nextScrollX, getMaxTextWidth() - paddingRight + drawablePadding); int x = Math.min(textOffsetX + textWidth + drawablePadding + (scrollingOffset == 0 ? -nextScrollX : (int) -scrollingOffset) + nextScrollX, getMaxTextWidth() - paddingRight + drawablePadding);
// When the text is centered/right-aligned, offsetX shifts the text horizontally.
// The outside drawable must follow the text, otherwise it overlaps the centered title.
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.CENTER_HORIZONTAL ||
(gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.RIGHT) {
x += offsetX;
}
int dw = (int) (rightDrawable.getIntrinsicWidth() * rightDrawableScale); int dw = (int) (rightDrawable.getIntrinsicWidth() * rightDrawableScale);
int dh = (int) (rightDrawable.getIntrinsicHeight() * rightDrawableScale); int dh = (int) (rightDrawable.getIntrinsicHeight() * rightDrawableScale);
int y; int y;
@ -1143,6 +1168,11 @@ public class SimpleTextView extends View implements Drawable.Callback {
textOffsetX + textWidth + drawablePadding + (scrollingOffset == 0 ? -nextScrollX : (int) -scrollingOffset) + nextScrollX, textOffsetX + textWidth + drawablePadding + (scrollingOffset == 0 ? -nextScrollX : (int) -scrollingOffset) + nextScrollX,
getMaxTextWidth() - paddingRight + drawablePadding getMaxTextWidth() - paddingRight + drawablePadding
); );
// Follow the centered/right-aligned text (see rightDrawable above).
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.CENTER_HORIZONTAL ||
(gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.RIGHT) {
x += offsetX;
}
if (rightDrawable != null) { if (rightDrawable != null) {
x += (int) (rightDrawable.getIntrinsicWidth() * rightDrawableScale) + drawablePadding; x += (int) (rightDrawable.getIntrinsicWidth() * rightDrawableScale) + drawablePadding;
} }
@ -1254,7 +1284,7 @@ public class SimpleTextView extends View implements Drawable.Callback {
lastUpdateTime = newUpdateTime; lastUpdateTime = newUpdateTime;
if (scrollingOffset > totalDistance) { if (scrollingOffset > totalDistance) {
scrollingOffset = 0; scrollingOffset = 0;
currentScrollDelay = SCROLL_DELAY_MS; currentScrollDelay = scrollDelayMs;
} }
checkUi_layerType(); checkUi_layerType();
} }

View file

@ -252,14 +252,14 @@ public class ThemeDescription {
if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) {
((CombinedDrawable) drawablesToUpdate[a]).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) drawablesToUpdate[a]).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
} else { } else {
((CombinedDrawable) drawablesToUpdate[a]).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) drawablesToUpdate[a]).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else if (drawablesToUpdate[a] instanceof AvatarDrawable) { } else if (drawablesToUpdate[a] instanceof AvatarDrawable) {
((AvatarDrawable) drawablesToUpdate[a]).setColor(color); ((AvatarDrawable) drawablesToUpdate[a]).setColor(color);
} else if (drawablesToUpdate[a] instanceof AnimatedArrowDrawable) { } else if (drawablesToUpdate[a] instanceof AnimatedArrowDrawable) {
((AnimatedArrowDrawable) drawablesToUpdate[a]).setColor(color); ((AnimatedArrowDrawable) drawablesToUpdate[a]).setColor(color);
} else { } else {
drawablesToUpdate[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); drawablesToUpdate[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} }
} }
@ -294,7 +294,7 @@ public class ThemeDescription {
} else if (drawable instanceof ShapeDrawable) { } else if (drawable instanceof ShapeDrawable) {
((ShapeDrawable) drawable).getPaint().setColor(color); ((ShapeDrawable) drawable).getPaint().setColor(color);
} else { } else {
drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} }
} }
@ -402,7 +402,7 @@ public class ThemeDescription {
Theme.setSelectorDrawableColor(drawable, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0); Theme.setSelectorDrawableColor(drawable, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0);
} }
} else { } else {
((ImageView) viewToInvalidate).setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((ImageView) viewToInvalidate).setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else if (viewToInvalidate instanceof BackupImageView) { } else if (viewToInvalidate instanceof BackupImageView) {
//((BackupImageView) viewToInvalidate).setResourceImageColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); //((BackupImageView) viewToInvalidate).setResourceImageColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
@ -414,7 +414,7 @@ public class ThemeDescription {
if (drawables != null) { if (drawables != null) {
for (int a = 0; a < drawables.length; a++) { for (int a = 0; a < drawables.length; a++) {
if (drawables[a] != null) { if (drawables[a] != null) {
drawables[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); drawables[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} }
} }
@ -525,7 +525,7 @@ public class ThemeDescription {
} else if (drawable instanceof StateListDrawable || Build.VERSION.SDK_INT >= 21 && drawable instanceof RippleDrawable) { } else if (drawable instanceof StateListDrawable || Build.VERSION.SDK_INT >= 21 && drawable instanceof RippleDrawable) {
Theme.setSelectorDrawableColor(drawable, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0); Theme.setSelectorDrawableColor(drawable, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0);
} }
drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} }
} else if ((changeFlags & FLAG_CELLBACKGROUNDCOLOR) != 0) { } else if ((changeFlags & FLAG_CELLBACKGROUNDCOLOR) != 0) {
@ -611,7 +611,7 @@ public class ThemeDescription {
if (drawables != null) { if (drawables != null) {
for (int a = 0; a < drawables.length; a++) { for (int a = 0; a < drawables.length; a++) {
if (drawables[a] != null) { if (drawables[a] != null) {
drawables[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); drawables[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} }
} }
@ -638,10 +638,10 @@ public class ThemeDescription {
if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) {
((CombinedDrawable) drawable).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) drawable).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
} else { } else {
((CombinedDrawable) drawable).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) drawable).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else { } else {
imageView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); imageView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else if (object instanceof BackupImageView) { } else if (object instanceof BackupImageView) {
Drawable drawable = ((BackupImageView) object).getImageReceiver().getStaticThumb(); Drawable drawable = ((BackupImageView) object).getImageReceiver().getStaticThumb();
@ -649,10 +649,10 @@ public class ThemeDescription {
if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) {
((CombinedDrawable) drawable).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) drawable).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
} else { } else {
((CombinedDrawable) drawable).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) drawable).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else if (drawable != null) { } else if (drawable != null) {
drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else if (object instanceof Drawable) { } else if (object instanceof Drawable) {
if (object instanceof LetterDrawable) { if (object instanceof LetterDrawable) {
@ -665,14 +665,14 @@ public class ThemeDescription {
if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) {
((CombinedDrawable) object).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) object).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
} else { } else {
((CombinedDrawable) object).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((CombinedDrawable) object).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else if (object instanceof StateListDrawable || Build.VERSION.SDK_INT >= 21 && object instanceof RippleDrawable) { } else if (object instanceof StateListDrawable || Build.VERSION.SDK_INT >= 21 && object instanceof RippleDrawable) {
Theme.setSelectorDrawableColor((Drawable) object, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0); Theme.setSelectorDrawableColor((Drawable) object, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0);
} else if (object instanceof GradientDrawable) { } else if (object instanceof GradientDrawable) {
((GradientDrawable) object).setColor(color); ((GradientDrawable) object).setColor(color);
} else { } else {
((Drawable) object).setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); ((Drawable) object).setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
} }
} else if (object instanceof CheckBox) { } else if (object instanceof CheckBox) {
if ((changeFlags & FLAG_CHECKBOX) != 0) { if ((changeFlags & FLAG_CHECKBOX) != 0) {

View file

@ -4301,7 +4301,7 @@ public class ChatActivity extends BaseFragment implements
}); });
getConnectionsManager().bindRequestToGuid(req, classGuid); getConnectionsManager().bindRequestToGuid(req, classGuid);
} else { } else {
actionBar.addView(avatarContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, !inPreviewMode ? 56 : (chatMode == MODE_PINNED ? 10 : 0), 0, 40, 0)); actionBar.addView(avatarContainer, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, !inPreviewMode ? 56 : (chatMode == MODE_PINNED ? 10 : 0), 0, 40, 0));
} }
ActionBarMenu menu = actionBar.createMenu(); ActionBarMenu menu = actionBar.createMenu();
@ -4335,7 +4335,8 @@ public class ChatActivity extends BaseFragment implements
audioCallIconItem = menu.lazilyAddItem(call, R.drawable.call, themeDelegate); audioCallIconItem = menu.lazilyAddItem(call, R.drawable.call, themeDelegate);
audioCallIconItem.setContentDescription(LocaleController.getString(R.string.Call)); audioCallIconItem.setContentDescription(LocaleController.getString(R.string.Call));
userFull = getMessagesController().getUserFull(currentUser.id); userFull = getMessagesController().getUserFull(currentUser.id);
if (userFull != null && userFull.phone_calls_available) { // In new layout calls are in the three-dots menu only, never as a toolbar icon
if (!tw.nekomimi.nekogram.NekoConfig.useNewLayout && userFull != null && userFull.phone_calls_available) {
showAudioCallAsIcon = !inPreviewMode; showAudioCallAsIcon = !inPreviewMode;
audioCallIconItem.setVisibility(View.VISIBLE); audioCallIconItem.setVisibility(View.VISIBLE);
} else { } else {
@ -23921,7 +23922,8 @@ public class ChatActivity extends BaseFragment implements
} }
} }
if (headerItem != null) { if (headerItem != null) {
showAudioCallAsIcon = userInfo.phone_calls_available && !inPreviewMode; // In new layout: calls always go in the three-dots menu, not as toolbar icon
showAudioCallAsIcon = !tw.nekomimi.nekogram.NekoConfig.useNewLayout && userInfo.phone_calls_available && !inPreviewMode;
if (avatarContainer != null) { if (avatarContainer != null) {
avatarContainer.setTitleExpand(showAudioCallAsIcon); avatarContainer.setTitleExpand(showAudioCallAsIcon);
} }
@ -29535,7 +29537,7 @@ public class ChatActivity extends BaseFragment implements
super.setInPreviewMode(value); super.setInPreviewMode(value);
if (currentUser != null && audioCallIconItem != null) { if (currentUser != null && audioCallIconItem != null) {
TLRPC.UserFull userFull = getMessagesController().getUserFull(currentUser.id); TLRPC.UserFull userFull = getMessagesController().getUserFull(currentUser.id);
if (userFull != null && userFull.phone_calls_available) { if (!tw.nekomimi.nekogram.NekoConfig.useNewLayout && userFull != null && userFull.phone_calls_available) {
showAudioCallAsIcon = !inPreviewMode; showAudioCallAsIcon = !inPreviewMode;
audioCallIconItem.setVisibility(View.VISIBLE); audioCallIconItem.setVisibility(View.VISIBLE);
} else { } else {

View file

@ -272,6 +272,8 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
titleTextView.setLeftDrawableTopPadding(-dp(1.3f)); titleTextView.setLeftDrawableTopPadding(-dp(1.3f));
titleTextView.setCanHideRightDrawable(false); titleTextView.setCanHideRightDrawable(false);
titleTextView.setRightDrawableOutside(true); titleTextView.setRightDrawableOutside(true);
// Marquee holds for 5s at the start of the title before scrolling (and after each loop).
titleTextView.setScrollDelayMs(5000);
titleTextView.setPadding(0, dp(6), 0, dp(12)); titleTextView.setPadding(0, dp(6), 0, dp(12));
addView(titleTextView); addView(titleTextView);
@ -644,13 +646,31 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec) + titleTextView.getPaddingRight(); int width = MeasureSpec.getSize(widthMeasureSpec) + titleTextView.getPaddingRight();
int availableWidth = width - dp((avatarImageView.getVisibility() == VISIBLE ? 54 : 0) + 16); final boolean centerMode = tw.nekomimi.nekogram.NekoConfig.useNewLayout && avatarImageView.getVisibility() == VISIBLE;
// Center mode layout:
// - avatar is on the right: dp(42) wide + dp(4) right gap = dp(46) from right edge
// - text area: from dp(8) (leftPadding) to (width - dp(46))
// - centerX of text area = leftPadding + (width - leftPadding - dp(46)) / 2
// We measure text with the full text-area width so AT_MOST gives true text width.
int availableWidth;
if (centerMode) {
// Full container width minus avatar (dp(46)) same zone as onLayout uses
availableWidth = width - dp(46);
} else {
availableWidth = width - dp((avatarImageView.getVisibility() == VISIBLE ? 54 : 0) + 16);
}
// Apply gravity BEFORE measuring children: SimpleTextView computes its horizontal
// text offset (offsetX) during measure based on the current gravity. Setting gravity
// only in onLayout has no effect because there's no re-measure afterwards, so the text
// stays left-aligned. Applying it here ensures calcOffset() centers the text in center mode.
applyGravityForMode();
avatarImageView.measure(MeasureSpec.makeMeasureSpec(dp(42), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(42), MeasureSpec.EXACTLY)); avatarImageView.measure(MeasureSpec.makeMeasureSpec(dp(42), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(42), MeasureSpec.EXACTLY));
titleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(dp(24 + 8) + titleTextView.getPaddingRight(), MeasureSpec.AT_MOST)); titleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(dp(24 + 8) + titleTextView.getPaddingRight(), MeasureSpec.AT_MOST));
if (subtitleTextView != null) { if (subtitleTextView != null) {
subtitleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(dp(20), MeasureSpec.AT_MOST)); subtitleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(dp(20), MeasureSpec.AT_MOST));
} else if (animatedSubtitleTextView != null) { } else if (animatedSubtitleTextView != null) {
animatedSubtitleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(20), MeasureSpec.AT_MOST)); // AT_MOST so getMeasuredWidth() reflects actual text width, not full available width
animatedSubtitleTextView.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(dp(20), MeasureSpec.AT_MOST));
} }
if (timeItem != null) { if (timeItem != null) {
timeItem.measure(MeasureSpec.makeMeasureSpec(dp(34), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(34), MeasureSpec.EXACTLY)); timeItem.measure(MeasureSpec.makeMeasureSpec(dp(34), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(34), MeasureSpec.EXACTLY));
@ -667,7 +687,12 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
} }
SimpleTextView titleTextLargerCopyView = this.titleTextLargerCopyView.get(); SimpleTextView titleTextLargerCopyView = this.titleTextLargerCopyView.get();
if (titleTextLargerCopyView != null) { if (titleTextLargerCopyView != null) {
int largerAvailableWidth = largerWidth - dp((avatarImageView.getVisibility() == VISIBLE ? 54 : 0) + 16); int largerAvailableWidth;
if (centerMode) {
largerAvailableWidth = largerWidth - dp(16) - dp(46);
} else {
largerAvailableWidth = largerWidth - dp((avatarImageView.getVisibility() == VISIBLE ? 54 : 0) + 16);
}
titleTextLargerCopyView.measure(MeasureSpec.makeMeasureSpec(largerAvailableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(dp(24), MeasureSpec.AT_MOST)); titleTextLargerCopyView.measure(MeasureSpec.makeMeasureSpec(largerAvailableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(dp(24), MeasureSpec.AT_MOST));
} }
lastWidth = width; lastWidth = width;
@ -734,37 +759,101 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int actionBarHeight = ActionBar.getCurrentActionBarHeight(); int actionBarHeight = ActionBar.getCurrentActionBarHeight();
int viewTop = (actionBarHeight - dp(42)) / 2 + (Build.VERSION.SDK_INT >= 21 && occupyStatusBar ? AndroidUtilities.statusBarHeight : 0); int viewTop = (actionBarHeight - dp(42)) / 2 + (Build.VERSION.SDK_INT >= 21 && occupyStatusBar ? AndroidUtilities.statusBarHeight : 0);
final boolean center = tw.nekomimi.nekogram.NekoConfig.useNewLayout && avatarImageView.getVisibility() == VISIBLE;
applyGravityForMode();
if (center) {
final int avatarRight = getMeasuredWidth() - dp(4);
avatarImageView.layout(avatarRight - dp(42), viewTop + 1, avatarRight, viewTop + 1 + dp(42));
} else {
avatarImageView.layout(leftPadding, viewTop + 1, leftPadding + dp(42), viewTop + 1 + dp(42)); avatarImageView.layout(leftPadding, viewTop + 1, leftPadding + dp(42), viewTop + 1 + dp(42));
int l = leftPadding + (avatarImageView.getVisibility() == VISIBLE ? dp( 54) : 0) + rightAvatarPadding; }
SimpleTextView titleTextLargerCopyView = this.titleTextLargerCopyView.get(); SimpleTextView titleTextLargerCopyView = this.titleTextLargerCopyView.get();
if (center) {
// The container spans exactly from button to menu (MATCH_PARENT with margins).
// So getMeasuredWidth() is the full available zone.
// Avatar takes dp(46) from the right text must not overlap it.
// Center of the full container = getMeasuredWidth() / 2.
final int W = getMeasuredWidth();
// Avatar sits at the right of the container. Text zone = [0 .. avatarEdge].
// We place BOTH title and subtitle views to span the full text zone [0..avatarEdge]
// and set gravity=CENTER_HORIZONTAL so each draws its text at the center of that zone.
// This guarantees identical visual center for both regardless of text length.
final int avatarEdge = W - dp(46);
if (getSubtitleTextView().getVisibility() != GONE) { if (getSubtitleTextView().getVisibility() != GONE) {
titleTextView.layout(l, viewTop + dp(1.3f) - titleTextView.getPaddingTop(), l + titleTextView.getMeasuredWidth(), viewTop + titleTextView.getTextHeight() + dp(1.3f) - titleTextView.getPaddingTop() + titleTextView.getPaddingBottom()); titleTextView.layout(0, viewTop + dp(1.3f) - titleTextView.getPaddingTop(),
avatarEdge, viewTop + titleTextView.getTextHeight() + dp(1.3f) - titleTextView.getPaddingTop() + titleTextView.getPaddingBottom());
if (titleTextLargerCopyView != null) {
titleTextLargerCopyView.setGravity(Gravity.CENTER_HORIZONTAL);
titleTextLargerCopyView.layout(0, viewTop + dp(1.3f), avatarEdge, viewTop + titleTextLargerCopyView.getTextHeight() + dp(1.3f));
}
if (subtitleTextView != null) {
subtitleTextView.layout(0, viewTop + dp(24), avatarEdge, viewTop + subtitleTextView.getTextHeight() + dp(24));
} else if (animatedSubtitleTextView != null) {
animatedSubtitleTextView.layout(0, viewTop + dp(24), avatarEdge, viewTop + animatedSubtitleTextView.getTextHeight() + dp(24));
}
} else {
titleTextView.layout(0, viewTop + dp(11) - titleTextView.getPaddingTop(),
avatarEdge, viewTop + titleTextView.getTextHeight() + dp(11) - titleTextView.getPaddingTop() + titleTextView.getPaddingBottom());
if (titleTextLargerCopyView != null) {
titleTextLargerCopyView.setGravity(Gravity.CENTER_HORIZONTAL);
titleTextLargerCopyView.layout(0, viewTop + dp(11), avatarEdge, viewTop + titleTextLargerCopyView.getTextHeight() + dp(11));
}
}
} else {
final int l = leftPadding + (avatarImageView.getVisibility() == VISIBLE ? dp(54) : 0) + rightAvatarPadding;
if (getSubtitleTextView().getVisibility() != GONE) {
titleTextView.layout(l, viewTop + dp(1.3f) - titleTextView.getPaddingTop(),
l + titleTextView.getMeasuredWidth(), viewTop + titleTextView.getTextHeight() + dp(1.3f) - titleTextView.getPaddingTop() + titleTextView.getPaddingBottom());
if (titleTextLargerCopyView != null) { if (titleTextLargerCopyView != null) {
titleTextLargerCopyView.layout(l, viewTop + dp(1.3f), l + titleTextLargerCopyView.getMeasuredWidth(), viewTop + titleTextLargerCopyView.getTextHeight() + dp(1.3f)); titleTextLargerCopyView.layout(l, viewTop + dp(1.3f), l + titleTextLargerCopyView.getMeasuredWidth(), viewTop + titleTextLargerCopyView.getTextHeight() + dp(1.3f));
} }
} else { } else {
titleTextView.layout(l, viewTop + dp(11) - titleTextView.getPaddingTop(), l + titleTextView.getMeasuredWidth(), viewTop + titleTextView.getTextHeight() + dp(11) - titleTextView.getPaddingTop() + titleTextView.getPaddingBottom()); titleTextView.layout(l, viewTop + dp(11) - titleTextView.getPaddingTop(),
l + titleTextView.getMeasuredWidth(), viewTop + titleTextView.getTextHeight() + dp(11) - titleTextView.getPaddingTop() + titleTextView.getPaddingBottom());
if (titleTextLargerCopyView != null) { if (titleTextLargerCopyView != null) {
titleTextLargerCopyView.layout(l, viewTop + dp(11), l + titleTextLargerCopyView.getMeasuredWidth(), viewTop + titleTextLargerCopyView.getTextHeight() + dp(11)); titleTextLargerCopyView.layout(l, viewTop + dp(11), l + titleTextLargerCopyView.getMeasuredWidth(), viewTop + titleTextLargerCopyView.getTextHeight() + dp(11));
} }
} }
if (timeItem != null) {
timeItem.layout(leftPadding + dp(16), viewTop + dp(15), leftPadding + dp(16 + 34), viewTop + dp(15 + 34));
}
if (starBgItem != null) {
starBgItem.layout(leftPadding + dp(28), viewTop + dp(24), leftPadding + dp(28) + starBgItem.getMeasuredWidth(), viewTop + dp(24) + starBgItem.getMeasuredHeight());
}
if (starFgItem != null) {
starFgItem.layout(leftPadding + dp(28), viewTop + dp(24), leftPadding + dp(28) + starFgItem.getMeasuredWidth(), viewTop + dp(24) + starFgItem.getMeasuredHeight());
}
if (subtitleTextView != null) { if (subtitleTextView != null) {
subtitleTextView.layout(l, viewTop + dp(24), l + subtitleTextView.getMeasuredWidth(), viewTop + subtitleTextView.getTextHeight() + dp(24)); subtitleTextView.layout(l, viewTop + dp(24), l + subtitleTextView.getMeasuredWidth(), viewTop + subtitleTextView.getTextHeight() + dp(24));
} else if (animatedSubtitleTextView != null) { } else if (animatedSubtitleTextView != null) {
animatedSubtitleTextView.layout(l, viewTop + dp(24), l + animatedSubtitleTextView.getMeasuredWidth(), viewTop + animatedSubtitleTextView.getTextHeight() + dp(24)); animatedSubtitleTextView.layout(l, viewTop + dp(24), l + animatedSubtitleTextView.getMeasuredWidth(), viewTop + animatedSubtitleTextView.getTextHeight() + dp(24));
} }
}
if (timeItem != null) {
if (center) {
final int avatarRight = getMeasuredWidth() - dp(4);
timeItem.layout(avatarRight - dp(42) + dp(16), viewTop + dp(15), avatarRight - dp(42) + dp(16 + 34), viewTop + dp(15 + 34));
} else {
timeItem.layout(leftPadding + dp(16), viewTop + dp(15), leftPadding + dp(16 + 34), viewTop + dp(15 + 34));
}
}
if (starBgItem != null) {
if (center) {
final int avatarRight = getMeasuredWidth() - dp(4);
starBgItem.layout(avatarRight - dp(42) + dp(28), viewTop + dp(24), avatarRight - dp(42) + dp(28) + starBgItem.getMeasuredWidth(), viewTop + dp(24) + starBgItem.getMeasuredHeight());
} else {
starBgItem.layout(leftPadding + dp(28), viewTop + dp(24), leftPadding + dp(28) + starBgItem.getMeasuredWidth(), viewTop + dp(24) + starBgItem.getMeasuredHeight());
}
}
if (starFgItem != null) {
if (center) {
final int avatarRight = getMeasuredWidth() - dp(4);
starFgItem.layout(avatarRight - dp(42) + dp(28), viewTop + dp(24), avatarRight - dp(42) + dp(28) + starFgItem.getMeasuredWidth(), viewTop + dp(24) + starFgItem.getMeasuredHeight());
} else {
starFgItem.layout(leftPadding + dp(28), viewTop + dp(24), leftPadding + dp(28) + starFgItem.getMeasuredWidth(), viewTop + dp(24) + starFgItem.getMeasuredHeight());
}
}
SimpleTextView subtitleTextLargerCopyView = this.subtitleTextLargerCopyView.get(); SimpleTextView subtitleTextLargerCopyView = this.subtitleTextLargerCopyView.get();
if (subtitleTextLargerCopyView != null) { if (!center && subtitleTextLargerCopyView != null) {
subtitleTextLargerCopyView.layout(l, viewTop + dp(24), l + subtitleTextLargerCopyView.getMeasuredWidth(), viewTop + subtitleTextLargerCopyView.getTextHeight() + dp(24)); final int l2 = leftPadding + (avatarImageView.getVisibility() == VISIBLE ? dp(54) : 0) + rightAvatarPadding;
subtitleTextLargerCopyView.layout(l2, viewTop + dp(24), l2 + subtitleTextLargerCopyView.getMeasuredWidth(), viewTop + subtitleTextLargerCopyView.getTextHeight() + dp(24));
} }
} }
@ -981,6 +1070,24 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
} }
/** Apply the correct gravity to title/subtitle based on current layout mode. */
private void applyGravityForMode() {
final boolean c = tw.nekomimi.nekogram.NekoConfig.useNewLayout && avatarImageView.getVisibility() == VISIBLE;
final int g = c ? Gravity.CENTER_HORIZONTAL : Gravity.LEFT;
titleTextView.setGravity(g);
if (subtitleTextView != null) subtitleTextView.setGravity(g);
if (animatedSubtitleTextView != null) animatedSubtitleTextView.setGravity(g);
// In center mode the right drawable (mute icon) must sit right after the title text and
// scroll together with it. "Outside" pins the drawable to the view's right edge instead,
// so disable it for center mode and keep stock behavior (pinned) otherwise.
titleTextView.setRightDrawableOutside(!c);
// Enable marquee for long titles in center mode. This must be set here (during measure,
// before SimpleTextView.calcOffset runs) rather than in onLayout, otherwise calcOffset
// computes the horizontal offset with a stale scroll flag and the paused text starts
// shifted instead of at the very beginning.
titleTextView.setScrollNonFitText(c);
}
public void setSubtitle(CharSequence value) { public void setSubtitle(CharSequence value) {
if (lastSubtitle == null) { if (lastSubtitle == null) {
if (subtitleTextView != null) { if (subtitleTextView != null) {
@ -988,6 +1095,7 @@ public class ChatAvatarContainer extends FrameLayout implements NotificationCent
} else if (animatedSubtitleTextView != null) { } else if (animatedSubtitleTextView != null) {
animatedSubtitleTextView.setText(value); animatedSubtitleTextView.setText(value);
} }
applyGravityForMode();
} else { } else {
lastSubtitle = value; lastSubtitle = value;
} }

View file

@ -38,6 +38,8 @@ public class UItem extends AdapterWithDiffUtils.Item {
public int pad; public int pad;
public boolean hideDivider; public boolean hideDivider;
public int iconResId; public int iconResId;
public boolean colorfulIcon;
public int iconColorTop, iconColorBottom;
public Drawable drawable; public Drawable drawable;
public CharSequence text, subtext, textValue; public CharSequence text, subtext, textValue;
public CharSequence animatedText; public CharSequence animatedText;
@ -693,6 +695,17 @@ public class UItem extends AdapterWithDiffUtils.Item {
return this; return this;
} }
public UItem colorfulIcon(int colorTop, int colorBottom) {
this.colorfulIcon = true;
this.iconColorTop = colorTop;
this.iconColorBottom = colorBottom;
return this;
}
public UItem colorfulIcon(org.telegram.ui.Components.IconBackgroundColors colors) {
return colorfulIcon(colors.top, colors.bottom);
}
public UItem setSpanCount(int spanCount) { public UItem setSpanCount(int spanCount) {
this.spanCount = spanCount; this.spanCount = spanCount;
return this; return this;

View file

@ -664,7 +664,13 @@ public class UniversalAdapter extends AdapterWithDiffUtils {
break; break;
case VIEW_TYPE_TEXT: case VIEW_TYPE_TEXT:
TextCell cell = (TextCell) holder.itemView; TextCell cell = (TextCell) holder.itemView;
if (item.object instanceof TLRPC.Document) { if (item.colorfulIcon && item.iconResId != 0) {
if (TextUtils.isEmpty(item.textValue)) {
cell.setText(item.text, divider);
} else {
cell.setTextAndValue(item.text, item.textValue, divider);
}
} else if (item.object instanceof TLRPC.Document) {
cell.setTextAndSticker(item.text, (TLRPC.Document) item.object, divider); cell.setTextAndSticker(item.text, (TLRPC.Document) item.object, divider);
} else if (item.object instanceof String) { } else if (item.object instanceof String) {
cell.setTextAndSticker(item.text, (String) item.object, divider); cell.setTextAndSticker(item.text, (String) item.object, divider);
@ -696,9 +702,15 @@ public class UniversalAdapter extends AdapterWithDiffUtils {
cell.setColors(Theme.key_windowBackgroundWhiteBlueText4, Theme.key_windowBackgroundWhiteBlueText4); cell.setColors(Theme.key_windowBackgroundWhiteBlueText4, Theme.key_windowBackgroundWhiteBlueText4);
} else if (item.red) { } else if (item.red) {
cell.setColors(Theme.key_text_RedBold, Theme.key_text_RedRegular); cell.setColors(Theme.key_text_RedBold, Theme.key_text_RedRegular);
} else if (item.colorfulIcon && item.iconResId != 0) {
// keep colorful gradient icon: only set text color, leave icon untouched
cell.setColors(-1, Theme.key_windowBackgroundWhiteBlackText);
} else { } else {
cell.setColors(Theme.key_windowBackgroundWhiteGrayIcon, Theme.key_windowBackgroundWhiteBlackText); cell.setColors(Theme.key_windowBackgroundWhiteGrayIcon, Theme.key_windowBackgroundWhiteBlackText);
} }
if (item.colorfulIcon && item.iconResId != 0) {
cell.setColorfulIcon(item.iconColorTop, item.iconColorBottom, item.iconResId);
}
cell.setEnabled(item.enabled, true); cell.setEnabled(item.enabled, true);
break; break;
case VIEW_TYPE_CHECK: case VIEW_TYPE_CHECK:

View file

@ -16,6 +16,7 @@ import android.text.TextPaint;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
@ -84,7 +85,7 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
imageView.setColorFilter(new PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN)); imageView.setColorFilter(new PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN));
textView = new TextView(context); textView = new TextView(context);
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12f); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 11f);
textView.setSingleLine(); textView.setSingleLine();
textView.setLines(1); textView.setLines(1);
textView.setEllipsize(TextUtils.TruncateAt.END); textView.setEllipsize(TextUtils.TruncateAt.END);
@ -152,7 +153,15 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
final float selectedFactor = hasGestureSelectedOverride ? gestureSelectedOverride : isSelectedAnimator.getFloatValue(); final float selectedFactor = hasGestureSelectedOverride ? gestureSelectedOverride : isSelectedAnimator.getFloatValue();
if (selectedFactor > 0 && !skipDrawSelector) { if (selectedFactor > 0 && !skipDrawSelector) {
final float alpha = AnimatorUtils.DECELERATE_INTERPOLATOR.getInterpolation(selectedFactor); final float alpha = AnimatorUtils.DECELERATE_INTERPOLATOR.getInterpolation(selectedFactor);
if (inlineTextMode) {
// Filled rounded background around the whole selected tab
final float inset = dpf2(4);
tmpRectF.set(inset, inset, viewWidth - inset, getHeight() - inset);
final float r = (tmpRectF.height()) / 2f;
paintCounterBackground.setStyle(Paint.Style.FILL);
paintCounterBackground.setColor(Theme.multAlpha(colorSelected, 0.15f * alpha));
canvas.drawRoundRect(tmpRectF, r, r, paintCounterBackground);
} else {
paintCounterBackground.setColor(Theme.multAlpha(colorSelected, 0.09f * alpha)); paintCounterBackground.setColor(Theme.multAlpha(colorSelected, 0.09f * alpha));
tmpRectF.set(0, 0, viewWidth, getHeight()); tmpRectF.set(0, 0, viewWidth, getHeight());
final float r = Math.min(tmpRectF.width(), tmpRectF.height()) / 2f; final float r = Math.min(tmpRectF.width(), tmpRectF.height()) / 2f;
@ -162,6 +171,7 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
canvas.drawRoundRect(tmpRectF, r, r, paintCounterBackground); canvas.drawRoundRect(tmpRectF, r, r, paintCounterBackground);
canvas.restore(); canvas.restore();
} }
}
final float hasCounter = (usePremiumCounter ? 1f : isHasCounterAnimator.getFloatValue()) * attachScale; final float hasCounter = (usePremiumCounter ? 1f : isHasCounterAnimator.getFloatValue()) * attachScale;
final boolean saveLayer = hasCounter > 0; final boolean saveLayer = hasCounter > 0;
@ -175,12 +185,20 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
canvas.save(); canvas.save();
final float gap = dpf2(1.33f); final float gap = dpf2(1.33f);
final float cx = viewWidth / 2f + dpf2(11); // In inline mode, position badge near the icon (right side of icon)
final float cx;
if (inlineTextMode) {
final int iconSize = dp(20);
final float iconLeft = (viewWidth - iconSize) / 2f;
cx = iconLeft + iconSize - dpf2(2);
} else {
cx = viewWidth / 2f + dpf2(11);
}
final float cy = dpf2(10); final float cy = dpf2(10);
final float height = dpf2(16); final float height = dpf2(14);
final float width = Math.max(height, counter.getCurrentWidth() + dp(8)); final float width = Math.max(height, counter.getCurrentWidth() + dp(6));
final float rOuter = dpf2(9.333f); final float rOuter = dpf2(8f);
final float rInner = dpf2(8f); final float rInner = dpf2(6.5f);
tmpRectF.set( tmpRectF.set(
cx - width / 2f - gap, cx - width / 2f - gap,
cy - height / 2f - gap, cy - height / 2f - gap,
@ -234,6 +252,116 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
checkPlayAnimation(animated); checkPlayAnimation(animated);
textView.setTypeface(selected ? AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_EXTRA_BOLD) : AndroidUtilities.bold()); textView.setTypeface(selected ? AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_EXTRA_BOLD) : AndroidUtilities.bold());
if (inlineTextMode) {
// layout is updated via onFactorChanged -> updateInlineLayout
} else if (hideTextWhenUnselected) {
updateTextVisibility(animated);
}
}
private boolean inlineTextMode; // when true: text appears to the right of icon
private boolean hideTextWhenUnselected;
private boolean classicTabMode; // when true: standard Telegram style 24dp icon centered above text
public void setInlineTextMode(boolean inline) {
this.inlineTextMode = inline;
requestLayout();
invalidate();
}
/**
* Classic Telegram bottom-nav style: icon 24dp centered horizontally near top,
* text label centered below it. No floating pill, full-width equal tabs.
*/
public void setClassicTabMode(boolean classic) {
this.classicTabMode = classic;
if (classic) {
// Reposition icon: 24dp, centered horizontally, 8dp from top
final FrameLayout.LayoutParams iconLp = (FrameLayout.LayoutParams) imageView.getLayoutParams();
iconLp.width = dp(24);
iconLp.height = dp(24);
iconLp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
iconLp.topMargin = dp(8);
imageView.setLayoutParams(iconLp);
// Reposition text: below icon, centered
final FrameLayout.LayoutParams textLp = (FrameLayout.LayoutParams) textView.getLayoutParams();
textLp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
textLp.topMargin = dp(34);
textView.setLayoutParams(textLp);
textView.setAlpha(1f);
textView.setScaleX(1f);
textView.setScaleY(1f);
}
requestLayout();
invalidate();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (inlineTextMode) {
updateInlineLayout(isSelectedAnimator.getFloatValue());
}
}
private void updateInlineLayout(float selectedFactor) {
if (!inlineTextMode) return;
final int w = getMeasuredWidth();
final int h = getMeasuredHeight();
final int iconSize = dp(20);
final int gap = dp(4);
// Use real text width from paint (textView is MATCH_PARENT so getMeasuredWidth is wrong)
final int textW = (int) Math.ceil(defaultTextPaint.measureText(textView.getText().toString()));
final int textH = textView.getMeasuredHeight();
// Icon always centered vertically
final int iconTop = (h - iconSize) / 2;
// Row (icon + gap + text) centered horizontally when expanded; icon centered when collapsed
final float expandedRowW = iconSize + gap + textW;
final float rowW = lerp(iconSize, expandedRowW, selectedFactor);
final float rowLeft = (w - rowW) / 2f;
final View iv = backupImageView != null && imageView.getVisibility() == GONE ? backupImageView : imageView;
iv.layout((int) rowLeft, iconTop, (int) rowLeft + iconSize, iconTop + iconSize);
if (backupImageView != null && imageView.getVisibility() == GONE) {
backupImageView.layout((int) rowLeft, iconTop, (int) rowLeft + iconSize, iconTop + iconSize);
}
// Text: right of icon, vertically centered, fade in.
// textView is gravity CENTER so we lay it out in a rect exactly text-wide.
final float textLeft = rowLeft + iconSize + gap;
final int textTop = (h - textH) / 2;
textView.layout((int) textLeft, textTop, (int) textLeft + textW, textTop + textH);
textView.setAlpha(selectedFactor);
textView.setScaleX(lerp(0.8f, 1f, selectedFactor));
textView.setScaleY(lerp(0.8f, 1f, selectedFactor));
textView.setPivotX(0);
textView.setPivotY(textH / 2f);
iv.setTranslationX(0);
iv.setTranslationY(0);
textView.setTranslationX(0);
textView.setTranslationY(0);
}
public void setHideTextWhenUnselected(boolean hide) {
this.hideTextWhenUnselected = hide;
updateTextVisibility(false);
}
private void updateTextVisibility(boolean animated) {
final boolean selected = isSelectedAnimator.getValue();
if (animated) {
textView.animate().cancel();
textView.animate()
.alpha(selected ? 1f : 0f)
.scaleX(selected ? 1f : 0.7f)
.scaleY(selected ? 1f : 0.7f)
.setDuration(220)
.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT)
.start();
} else {
textView.setAlpha(selected ? 1f : 0f);
textView.setScaleX(selected ? 1f : 0.7f);
textView.setScaleY(selected ? 1f : 0.7f);
}
} }
public boolean isTabSelected() { public boolean isTabSelected() {
@ -244,6 +372,12 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
public void onFactorChanged(int id, float factor, float fraction, FactorAnimator callee) { public void onFactorChanged(int id, float factor, float fraction, FactorAnimator callee) {
if (id == ANIMATOR_ID_IS_SELECTED) { if (id == ANIMATOR_ID_IS_SELECTED) {
updateColors(); updateColors();
if (inlineTextMode) {
updateInlineLayout(factor);
if (getParent() instanceof View) {
((View) getParent()).requestLayout();
}
}
} }
invalidate(); invalidate();
} }
@ -396,6 +530,21 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
tab.tabAnimation = tabAnimation; tab.tabAnimation = tabAnimation;
tab.textView.setText(LocaleController.getString(stringRes)); tab.textView.setText(LocaleController.getString(stringRes));
tab.checkPlayAnimation(false); tab.checkPlayAnimation(false);
tab.imageView.setLayoutParams(LayoutHelper.createFrame(20, 20, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 4, 0, 0));
tab.colorDefault = Theme.getColor(Theme.key_glass_tabUnselected, resourcesProvider);
tab.colorSelected = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider);
tab.colorSelectedText = Theme.getColor(Theme.key_glass_tabSelectedText, resourcesProvider);
tab.updateColors();
return tab;
}
public static GlassTabView createMainTabIcon(Context context, Theme.ResourcesProvider resourcesProvider, @androidx.annotation.DrawableRes int iconRes, @StringRes int stringRes) {
GlassTabView tab = new GlassTabView(context);
tab.resourcesProvider = resourcesProvider;
tab.tabAnimation = null;
tab.textView.setText(LocaleController.getString(stringRes));
tab.imageView.clearAnimationDrawable();
tab.imageView.setImageResource(iconRes);
tab.imageView.setLayoutParams(LayoutHelper.createFrame(24, 24, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 4, 0, 0)); tab.imageView.setLayoutParams(LayoutHelper.createFrame(24, 24, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 4, 0, 0));
tab.colorDefault = Theme.getColor(Theme.key_glass_tabUnselected, resourcesProvider); tab.colorDefault = Theme.getColor(Theme.key_glass_tabUnselected, resourcesProvider);
tab.colorSelected = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider); tab.colorSelected = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider);
@ -404,6 +553,61 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
return tab; return tab;
} }
public static GlassTabView createComposeButton(Context context, Theme.ResourcesProvider resourcesProvider, @androidx.annotation.DrawableRes int iconRes) {
return createComposeButton(context, resourcesProvider, iconRes, 32);
}
public static GlassTabView createComposeButton(Context context, Theme.ResourcesProvider resourcesProvider, @androidx.annotation.DrawableRes int iconRes, int iconSizeDp) {
GlassTabView tab = new GlassTabView(context);
tab.resourcesProvider = resourcesProvider;
tab.tabAnimation = null;
tab.textView.setVisibility(GONE);
tab.imageView.clearAnimationDrawable();
tab.imageView.setImageResource(iconRes);
tab.imageView.setLayoutParams(LayoutHelper.createFrame(iconSizeDp, iconSizeDp, Gravity.CENTER, 0, 0, 0, 0));
tab.colorDefault = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider);
tab.colorSelected = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider);
tab.colorSelectedText = Theme.getColor(Theme.key_glass_tabSelectedText, resourcesProvider);
tab.updateColors();
return tab;
}
public static GlassTabView createMainTabAnimatedIcon(Context context, Theme.ResourcesProvider resourcesProvider, @RawRes int animationRes, @StringRes int stringRes) {
GlassTabView tab = new GlassTabView(context);
tab.resourcesProvider = resourcesProvider;
tab.tabAnimation = null;
tab.isPlayOnceIcon = true;
tab.textView.setText(LocaleController.getString(stringRes));
tab.imageView.setAnimation(animationRes, 20, 20);
final RLottieDrawable drawable = tab.imageView.getAnimatedDrawable();
if (drawable != null) {
drawable.setPlayInDirectionOfCustomEndFrame(true);
drawable.setCurrentFrame(drawable.getFramesCount() - 1, false);
}
tab.imageView.setLayoutParams(LayoutHelper.createFrame(20, 20, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 4, 0, 0));
tab.colorDefault = Theme.getColor(Theme.key_glass_tabUnselected, resourcesProvider);
tab.colorSelected = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider);
tab.colorSelectedText = Theme.getColor(Theme.key_glass_tabSelectedText, resourcesProvider);
tab.updateColors();
return tab;
}
private boolean isPlayOnceIcon;
public void playIconAnimationOnce() {
if (!isPlayOnceIcon) {
return;
}
final RLottieDrawable drawable = imageView.getAnimatedDrawable();
if (drawable == null) {
return;
}
drawable.setPlayInDirectionOfCustomEndFrame(false);
drawable.setCustomEndFrame(drawable.getFramesCount());
drawable.setCurrentFrame(0, false);
imageView.playAnimation();
}
public static GlassTabView createAvatar(Context context, Theme.ResourcesProvider resourcesProvider, int currentAccount, @StringRes int stringRes) { public static GlassTabView createAvatar(Context context, Theme.ResourcesProvider resourcesProvider, int currentAccount, @StringRes int stringRes) {
GlassTabView tab = new GlassTabView(context); GlassTabView tab = new GlassTabView(context);
tab.textView.setText(LocaleController.getString(stringRes)); tab.textView.setText(LocaleController.getString(stringRes));
@ -417,7 +621,7 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
backupImageView.setRoundRadius(dp(11)); backupImageView.setRoundRadius(dp(11));
tab.backupImageView = backupImageView; tab.backupImageView = backupImageView;
tab.addView(backupImageView, LayoutHelper.createFrame(22, 22, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 5, 0, 0)); tab.addView(backupImageView, LayoutHelper.createFrame(20, 20, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 5, 0, 0));
tab.colorDefault = Theme.getColor(Theme.key_glass_tabUnselected, resourcesProvider); tab.colorDefault = Theme.getColor(Theme.key_glass_tabUnselected, resourcesProvider);
tab.colorSelected = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider); tab.colorSelected = Theme.getColor(Theme.key_glass_tabSelected, resourcesProvider);
tab.colorSelectedText = Theme.getColor(Theme.key_glass_tabSelectedText, resourcesProvider); tab.colorSelectedText = Theme.getColor(Theme.key_glass_tabSelectedText, resourcesProvider);
@ -507,7 +711,13 @@ public class GlassTabView extends FrameLayout implements MainTabsLayout.Tab, Fac
@Override @Override
public float measureTextWidth() { public float measureTextWidth() {
return defaultTextPaint.measureText(textView.getText().toString()); final float textW = defaultTextPaint.measureText(textView.getText().toString());
if (inlineTextMode) {
// Selected tab needs room for icon+gap+text; unselected only needs the icon.
final float factor = isSelectedAnimator.getFloatValue();
return lerp(dp(20), dp(20) + dp(4) + textW, factor);
}
return textW;
} }

View file

@ -425,6 +425,8 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati
} }
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
setTheme(R.style.Theme_TMessages); setTheme(R.style.Theme_TMessages);
// Apply custom font to all TextViews inflated from XML
tw.nekomimi.nekogram.helpers.FontHelper.installFactory(this);
try { try {
setTaskDescription(new ActivityManager.TaskDescription(null, null, Theme.getColor(Theme.key_actionBarDefault) | 0xff000000)); setTaskDescription(new ActivityManager.TaskDescription(null, null, Theme.getColor(Theme.key_actionBarDefault) | 0xff000000));
} catch (Throwable ignore) { } catch (Throwable ignore) {

View file

@ -32,7 +32,6 @@ import androidx.core.view.WindowInsetsCompat;
import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.BuildConfig; import org.telegram.messenger.BuildConfig;
import org.telegram.messenger.ContactsController;
import org.telegram.messenger.Emoji; import org.telegram.messenger.Emoji;
import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog; import org.telegram.messenger.FileLog;
@ -83,20 +82,27 @@ import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.helpers.PasscodeHelper; import tw.nekomimi.nekogram.helpers.PasscodeHelper;
public class MainTabsActivity extends ViewPagerActivity implements NotificationCenter.NotificationCenterDelegate, FactorAnimator.Target { public class MainTabsActivity extends ViewPagerActivity implements NotificationCenter.NotificationCenterDelegate, FactorAnimator.Target {
public static final int TABS_COUNT = 4; public static final int TABS_COUNT = 3;
private static final int POSITION_CHATS = 0; private static final int POSITION_CHATS = 0;
private static final int POSITION_CONTACTS = 1; private static final int POSITION_PROFILE = 1;
private static final int POSITION_CALLS_OR_SETTINGS = 2; private static final int POSITION_SETTINGS = 2;
private static final int POSITION_PROFILE = 3;
private static final int INDEX_CHATS = 0; private static final int INDEX_CHATS = 0;
private static final int INDEX_CONTACTS = 1; private static final int INDEX_SEARCH = 1;
private static final int INDEX_SETTINGS = 2; private static final int INDEX_PROFILE = 2;
private static final int INDEX_CALLS = 3; private static final int INDEX_SETTINGS = 3;
private static final int INDEX_PROFILE = 4;
private static int indexToPosition(int index) { private static int indexToPosition(int index) {
return index > 2 ? index - 1 : index; switch (index) {
case INDEX_CHATS:
return POSITION_CHATS;
case INDEX_PROFILE:
return POSITION_PROFILE;
case INDEX_SETTINGS:
return POSITION_SETTINGS;
default:
return -1; // search tab has no page
}
} }
@ -107,11 +113,13 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
private IUpdateLayout updateLayout; private IUpdateLayout updateLayout;
private boolean dropCallsFragmentAfterPageScroll;
private UpdateLayoutWrapper updateLayoutWrapper; private UpdateLayoutWrapper updateLayoutWrapper;
private FrameLayout tabsViewWrapper; private FrameLayout tabsViewWrapper;
private MainTabsLayout tabsView; private MainTabsLayout tabsView;
private GlassTabView composeButton;
private View tabsSpacer;
private View tabsLeadingSpacer;
private BlurredBackgroundDrawable tabsViewBackground; private BlurredBackgroundDrawable tabsViewBackground;
private View fadeView; private View fadeView;
@ -212,7 +220,6 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
blur3_updateColors(); blur3_updateColors();
checkContactsTabBadge();
checkUnreadCount(true); checkUnreadCount(true);
Bulletin.Delegate delegate = new Bulletin.Delegate() { Bulletin.Delegate delegate = new Bulletin.Delegate() {
@ -228,20 +235,6 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
showAccountChangeHint(); showAccountChangeHint();
} }
private void checkContactsTabBadge() {
if (tabsView != null && tabs[INDEX_CONTACTS] != null) {
final boolean hasPermission = Build.VERSION.SDK_INT >= 23 && ContactsController.hasContactsPermission();
if (hasPermission) {
MessagesController.getGlobalNotificationsSettings().edit().putBoolean("askAboutContacts2", true).apply();
}
if (Build.VERSION.SDK_INT >= 23 && UserConfig.getInstance(currentAccount).syncContacts && !hasPermission && MessagesController.getGlobalNotificationsSettings().getBoolean("askAboutContacts2", true)) {
tabs[INDEX_CONTACTS].setCounter("!", true, true);
} else {
tabs[INDEX_CONTACTS].setCounter(null, true, true);
}
}
}
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
@ -258,23 +251,33 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
tabsView = new MainTabsLayout(context, resourceProvider); tabsView = new MainTabsLayout(context, resourceProvider);
tabsView.setClipChildren(false); tabsView.setClipChildren(false);
// New layout: compact inline tabs (text beside icon). Classic layout: text below the icon.
final boolean inlineTabs = tw.nekomimi.nekogram.NekoConfig.useNewLayout;
tabsView.setPadding(dp(DialogsActivity.MAIN_TABS_MARGIN + 4), dp(DialogsActivity.MAIN_TABS_MARGIN + 4), dp(DialogsActivity.MAIN_TABS_MARGIN + 4), dp(DialogsActivity.MAIN_TABS_MARGIN + 4)); tabsView.setPadding(dp(DialogsActivity.MAIN_TABS_MARGIN + 4), dp(DialogsActivity.MAIN_TABS_MARGIN + 4), dp(DialogsActivity.MAIN_TABS_MARGIN + 4), dp(DialogsActivity.MAIN_TABS_MARGIN + 4));
tabs = new GlassTabView[5]; tabs = new GlassTabView[4];
tabs[INDEX_CHATS] = GlassTabView.createMainTab(context, resourceProvider, GlassTabView.TabAnimation.CHATS, R.string.MainTabsChats); tabs[INDEX_CHATS] = GlassTabView.createMainTab(context, resourceProvider, GlassTabView.TabAnimation.CHATS, R.string.MainTabsChats);
tabs[INDEX_CONTACTS] = GlassTabView.createMainTab(context, resourceProvider, GlassTabView.TabAnimation.CONTACTS, R.string.MainTabsContacts); tabs[INDEX_SEARCH] = GlassTabView.createMainTabAnimatedIcon(context, resourceProvider, R.raw.options_to_search, R.string.Search);
tabs[INDEX_SETTINGS] = GlassTabView.createMainTab(context, resourceProvider, GlassTabView.TabAnimation.SETTINGS, R.string.Settings);
tabs[INDEX_CALLS] = GlassTabView.createMainTab(context, resourceProvider, GlassTabView.TabAnimation.CALLS, R.string.MainTabsCalls);
tabs[INDEX_PROFILE] = GlassTabView.createAvatar(context, resourceProvider, currentAccount, R.string.MainTabsProfile); tabs[INDEX_PROFILE] = GlassTabView.createAvatar(context, resourceProvider, currentAccount, R.string.MainTabsProfile);
tabs[INDEX_SETTINGS] = GlassTabView.createMainTab(context, resourceProvider, GlassTabView.TabAnimation.SETTINGS, R.string.Settings);
tabs[INDEX_CHATS].setInlineTextMode(inlineTabs);
tabs[INDEX_SEARCH].setInlineTextMode(inlineTabs);
tabs[INDEX_PROFILE].setInlineTextMode(inlineTabs);
tabs[INDEX_SETTINGS].setInlineTextMode(inlineTabs);
if (!inlineTabs) {
// Classic Telegram style: 24dp icon on top, text label below
tabs[INDEX_CHATS].setClassicTabMode(true);
tabs[INDEX_SEARCH].setClassicTabMode(true);
tabs[INDEX_PROFILE].setClassicTabMode(true);
tabs[INDEX_SETTINGS].setClassicTabMode(true);
}
tabs[INDEX_CHATS].setOnLongClickListener(this::openFoldersSelector); tabs[INDEX_CHATS].setOnLongClickListener(this::openFoldersSelector);
tabs[INDEX_CONTACTS].setOnLongClickListener(this::openContactsSelector);
tabs[INDEX_CALLS].setOnLongClickListener(this::openCallsSelector);
tabs[INDEX_PROFILE].setOnLongClickListener(this::openAccountSelector); tabs[INDEX_PROFILE].setOnLongClickListener(this::openAccountSelector);
tabsView.addTabToIgnoreClick(tabs[INDEX_CHATS]); tabsView.addTabToIgnoreClick(tabs[INDEX_CHATS]);
tabsView.addTabToIgnoreClick(tabs[INDEX_CONTACTS]); tabsView.addTabToIgnoreClick(tabs[INDEX_SEARCH]);
tabsView.addTabToIgnoreClick(tabs[INDEX_PROFILE]); tabsView.addTabToIgnoreClick(tabs[INDEX_PROFILE]);
tabsView.addTabToIgnoreClick(tabs[INDEX_CALLS]); tabsView.addTabToIgnoreClick(tabs[INDEX_SETTINGS]);
for (int index = 0; index < tabs.length; index++) { for (int index = 0; index < tabs.length; index++) {
final GlassTabView view = tabs[index]; final GlassTabView view = tabs[index];
@ -285,6 +288,12 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
return; return;
} }
if (position < 0) {
// Search tab: open search inside the chats list
onSearchTabClick();
return;
}
if (viewPager.getCurrentPosition() == position) { if (viewPager.getCurrentPosition() == position) {
final BaseFragment fragment = getCurrentVisibleFragment(); final BaseFragment fragment = getCurrentVisibleFragment();
if (fragment instanceof MainTabsActivity.TabFragmentDelegate) { if (fragment instanceof MainTabsActivity.TabFragmentDelegate) {
@ -300,7 +309,36 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
tabsView.addView(tabs[index]); tabsView.addView(tabs[index]);
tabsView.setViewVisible(view, true, false); tabsView.setViewVisible(view, true, false);
} }
checkUi_callTabVisible(getUserConfig().showCallsTab, false);
// No leading spacer tabs are left-aligned, trailing spacer pushes compose to the right.
// Classic mode also skips leading spacer (pill centered by wrapper gravity instead).
// Flexible spacer pushes the compose button to the right edge of the bar
// In classic mode the spacer and compose button are hidden pill wraps the 4 tabs only.
tabsSpacer = new View(context);
tabsView.setSpacerView(tabsSpacer);
tabsView.addView(tabsSpacer);
tabsView.setViewVisible(tabsSpacer, inlineTabs, false);
tabsView.addTabToIgnoreClick(tabsSpacer);
// Compose button lives inside the same glass bar, on the right.
// Classic mode: hidden from tab bar (pill must wrap 4 tabs only).
composeButton = GlassTabView.createComposeButton(context, resourceProvider, R.drawable.filled_fab_compose_32, inlineTabs ? 32 : 40);
composeButton.setOnClickListener(v -> {
if (viewPager.isManualScrolling() || viewPager.isTouch()) {
return;
}
onComposeClick();
});
tabsView.addTabToIgnoreClick(composeButton);
tabsView.addView(composeButton);
tabsView.setViewVisible(composeButton, inlineTabs, false);
// Fill the full width only in new layout; classic pill wraps its own content
tabsView.setMinTotalWidthDp(0);
if (inlineTabs) {
tabsView.setFillParentWidth(true);
}
selectTab(viewPager.getCurrentPosition(), false); selectTab(viewPager.getCurrentPosition(), false);
@ -332,8 +370,14 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
tabsViewWrapper = new FrameLayout(context); tabsViewWrapper = new FrameLayout(context);
tabsViewWrapper.setOnClickListener(v -> {}); tabsViewWrapper.setOnClickListener(v -> {});
tabsViewWrapper.addView(tabsView, LayoutHelper.createFrame(328 + DialogsActivity.MAIN_TABS_MARGIN * 2, DialogsActivity.MAIN_TABS_HEIGHT_WITH_MARGINS, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL)); if (inlineTabs) {
tabsViewWrapper.addView(tabsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, DialogsActivity.MAIN_TABS_HEIGHT_WITH_MARGINS, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL, DialogsActivity.MAIN_TABS_MARGIN, 0, DialogsActivity.MAIN_TABS_MARGIN, 0));
} else {
// Classic mode: pill wraps its content and is centered horizontally
tabsViewWrapper.addView(tabsView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, DialogsActivity.MAIN_TABS_HEIGHT_WITH_MARGINS, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL));
}
tabsViewWrapper.setClipToPadding(false); tabsViewWrapper.setClipToPadding(false);
contentView.addView(tabsViewWrapper, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM)); contentView.addView(tabsViewWrapper, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM));
updateLayoutWrapper = new UpdateLayoutWrapper(context); updateLayoutWrapper = new UpdateLayoutWrapper(context);
@ -363,50 +407,7 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
} }
public boolean openContactsSelector(View anchor) { public boolean openContactsSelector(View anchor) {
if (getContext() == null || getParentActivity() == null) return false; return false;
final ItemOptions o = ItemOptions.makeOptions(this, anchor);
o.add(R.drawable.msg_contact_add, getString(R.string.NewContact), () -> {
new NewContactBottomSheet(this, getContext()).show();
});
o.add(R.drawable.msg_calls, getString(R.string.VoipChatRecentCalls), () -> {
Bundle args = new Bundle();
args.putBoolean("needFinishFragment", false);
presentFragment(new CallLogActivity(args));
});
o.setBlur(true);
o.translate(0, -dp(4));
o.setGravity(Gravity.LEFT);
final ShapeDrawable bg = Theme.createRoundRectDrawable(dp(28), getThemedColor(Theme.key_windowBackgroundWhite));
bg.getPaint().setShadowLayer(dp(6), 0, dp(1), Theme.multAlpha(0xFF000000, 0.15f));
o.setScrimViewBackground(bg);
o.show();
return true;
}
public boolean openCallsSelector(View anchor) {
if (getContext() == null || getParentActivity() == null) return false;
final ItemOptions o = ItemOptions.makeOptions(this, anchor);
o.add(R.drawable.menu_call_create, getString(R.string.GroupCallCreate2), () -> CallLogActivity.openCreateCall(this));
if (getUserConfig().showCallsTab) {
o.add(R.drawable.msg_archive_hide, getString(R.string.HideCallTab), () -> {
getUserConfig().setShowCallsTab(false);
checkUi_callTabVisible(false, true);
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.callTabsVisibleToggled);
});
} else {
o.add(R.drawable.menu_add_tab_24, getString(R.string.GroupCallShowInMainTabs), () -> {
getUserConfig().setShowCallsTab(true);
checkUi_callTabVisible(true, true);
NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.callTabsVisibleToggled);
});
}
o.setBlur(true);
o.translate(0, -dp(4));
final ShapeDrawable bg = Theme.createRoundRectDrawable(dp(28), getThemedColor(Theme.key_windowBackgroundWhite));
bg.getPaint().setShadowLayer(dp(6), 0, dp(1), Theme.multAlpha(0xFF000000, 0.15f));
o.setScrimViewBackground(bg);
o.show();
return true;
} }
private Integer pendingFolderId; private Integer pendingFolderId;
@ -457,8 +458,38 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
return true; return true;
} }
private void openFolder(int folderId) { private void onSearchTabClick() {
if (viewPager.getCurrentPosition() == POSITION_CHATS && dialogsActivity != null) { tabs[INDEX_SEARCH].playIconAnimationOnce();
if (dialogsActivity == null) {
prepareDialogsActivity(null);
}
if (viewPager.getCurrentPosition() != POSITION_CHATS) {
selectTab(POSITION_CHATS, true);
viewPager.scrollToPosition(POSITION_CHATS);
AndroidUtilities.runOnUIThread(() -> {
if (dialogsActivity != null) {
dialogsActivity.openSearch();
}
}, 200);
} else if (dialogsActivity != null) {
dialogsActivity.openSearch();
}
}
private void onComposeClick() {
if (dialogsActivity == null) {
prepareDialogsActivity(null);
}
if (viewPager.getCurrentPosition() != POSITION_CHATS) {
selectTab(POSITION_CHATS, true);
viewPager.scrollToPosition(POSITION_CHATS);
}
if (dialogsActivity != null) {
dialogsActivity.performComposeClick();
}
}
private void openFolder(int folderId) { if (viewPager.getCurrentPosition() == POSITION_CHATS && dialogsActivity != null) {
dialogsActivity.scrollToFolder(folderId); dialogsActivity.scrollToFolder(folderId);
} else { } else {
if (dialogsActivity == null) { if (dialogsActivity == null) {
@ -603,10 +634,6 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
if (viewPager != null) { if (viewPager != null) {
final int currentPosition = viewPager.getCurrentPosition(); final int currentPosition = viewPager.getCurrentPosition();
if (currentPosition != POSITION_CALLS_OR_SETTINGS && dropCallsFragmentAfterPageScroll) {
dropFragmentAtPosition(POSITION_CALLS_OR_SETTINGS);
dropCallsFragmentAfterPageScroll = false;
}
if (currentPosition != POSITION_PROFILE) { if (currentPosition != POSITION_PROFILE) {
dropFragmentAtPosition(POSITION_PROFILE); dropFragmentAtPosition(POSITION_PROFILE);
} }
@ -676,19 +703,7 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
@Override @Override
protected BaseFragment createBaseFragmentAt(int position) { protected BaseFragment createBaseFragmentAt(int position) {
if (position == POSITION_CONTACTS) { if (position == POSITION_SETTINGS) {
Bundle args = new Bundle();
args.putBoolean("needPhonebook", true);
args.putBoolean("needFinishFragment", false);
args.putBoolean("hasMainTabs", true);
return new ContactsActivity(args);
} else if (position == POSITION_CALLS_OR_SETTINGS) {
if (getUserConfig().showCallsTab) {
Bundle args = new Bundle();
args.putBoolean("needFinishFragment", false);
args.putBoolean("hasMainTabs", true);
return new CallLogActivity(args);
}
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putBoolean("hasMainTabs", true); args.putBoolean("hasMainTabs", true);
return new SettingsActivity(args); return new SettingsActivity(args);
@ -761,7 +776,7 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
} }
private boolean canScrollInternal(MotionEvent ev, boolean forward) { private boolean canScrollInternal(MotionEvent ev, boolean forward) {
if (NekoConfig.hideBottomNavigationBar) { if (NekoConfig.isBottomNavigationBarHidden()) {
return false; return false;
} }
final BaseFragment fragment = getCurrentVisibleFragment(); final BaseFragment fragment = getCurrentVisibleFragment();
@ -789,7 +804,7 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
ViewGroup.MarginLayoutParams lp; ViewGroup.MarginLayoutParams lp;
{ {
final int height = navigationBarHeight + updateLayoutHeight + dp(NekoConfig.hideBottomNavigationBar ? 0 : DialogsActivity.MAIN_TABS_HEIGHT_WITH_MARGINS); final int height = navigationBarHeight + updateLayoutHeight + dp(NekoConfig.isBottomNavigationBarHidden() ? 0 : DialogsActivity.MAIN_TABS_HEIGHT_WITH_MARGINS);
lp = (ViewGroup.MarginLayoutParams) fadeView.getLayoutParams(); lp = (ViewGroup.MarginLayoutParams) fadeView.getLayoutParams();
if (lp.height != height) { if (lp.height != height) {
lp.height = height; lp.height = height;
@ -851,22 +866,10 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
} }
} else if (id == NotificationCenter.needSetDayNightTheme) { } else if (id == NotificationCenter.needSetDayNightTheme) {
clearAllHiddenFragments(); clearAllHiddenFragments();
} else if (id == NotificationCenter.callTabsVisibleToggled) {
final boolean callTabsVisible = getUserConfig().showCallsTab;
checkUi_callTabVisible(callTabsVisible, true);
if (viewPager != null && viewPager.getCurrentPosition() == POSITION_CALLS_OR_SETTINGS) {
viewPager.scrollToPosition(POSITION_CHATS);
selectTab(POSITION_CHATS, true);
dropCallsFragmentAfterPageScroll = true;
} else {
dropFragmentAtPosition(POSITION_CALLS_OR_SETTINGS);
}
} else if (id == NotificationCenter.mainUserInfoChanged) { } else if (id == NotificationCenter.mainUserInfoChanged) {
if (tabs != null && tabs[INDEX_PROFILE] != null) { if (tabs != null && tabs[INDEX_PROFILE] != null) {
tabs[INDEX_PROFILE].updateUserAvatar(currentAccount); tabs[INDEX_PROFILE].updateUserAvatar(currentAccount);
} }
} else if (id == NotificationCenter.contactsPermissionBadgeCheck) {
checkContactsTabBadge();
} }
} }
@ -882,9 +885,7 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
.add(NotificationCenter.fileLoadFailed) .add(NotificationCenter.fileLoadFailed)
.add(NotificationCenter.notificationsCountUpdated) .add(NotificationCenter.notificationsCountUpdated)
.add(NotificationCenter.updateInterfaces) .add(NotificationCenter.updateInterfaces)
.add(NotificationCenter.callTabsVisibleToggled) .add(NotificationCenter.mainUserInfoChanged);
.add(NotificationCenter.mainUserInfoChanged)
.add(NotificationCenter.contactsPermissionBadgeCheck);
globalObserversGroup = NotificationCenter.getGlobalInstance().createObserversGroup(this) globalObserversGroup = NotificationCenter.getGlobalInstance().createObserversGroup(this)
.add(NotificationCenter.appUpdateAvailable) .add(NotificationCenter.appUpdateAvailable)
@ -916,7 +917,7 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
} }
private void checkUi_fadeView() { private void checkUi_fadeView() {
if (viewPager == null || fadeView == null || NekoConfig.hideBottomNavigationBar) { if (viewPager == null || fadeView == null || NekoConfig.isBottomNavigationBarHidden()) {
return; return;
} }
@ -931,8 +932,9 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
} }
private void checkUi_tabsPosition() { private void checkUi_tabsPosition() {
if (NekoConfig.hideBottomNavigationBar) { if (NekoConfig.isBottomNavigationBarHidden()) {
tabsView.setVisibility(View.GONE); tabsView.setVisibility(View.GONE);
if (composeButton != null) composeButton.setVisibility(View.GONE);
return; return;
} }
final boolean isUpdateLayoutVisible = updateLayoutWrapper.isUpdateLayoutVisible(); final boolean isUpdateLayoutVisible = updateLayoutWrapper.isUpdateLayoutVisible();
@ -941,21 +943,15 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
final int hiddenY = normalY + dp(40); final int hiddenY = normalY + dp(40);
final float factor = animatorTabsVisible.getFloatValue(); final float factor = animatorTabsVisible.getFloatValue();
final float scale = lerp(0.85f, 1f, factor);
tabsViewWrapper.setTranslationY(lerp(hiddenY, normalY, factor)); tabsViewWrapper.setTranslationY(lerp(hiddenY, normalY, factor));
//tabsView.setScaleX(scale);
//tabsView.setScaleY(scale);
tabsView.setClickable(factor > 1); tabsView.setClickable(factor > 1);
tabsView.setEnabled(factor > 1); tabsView.setEnabled(factor > 1);
tabsView.setAlpha(factor); tabsView.setAlpha(factor);
tabsView.setVisibility(factor > 0 ? View.VISIBLE : View.GONE); tabsView.setVisibility(factor > 0 ? View.VISIBLE : View.GONE);
} if (composeButton != null) {
composeButton.setAlpha(factor);
private void checkUi_callTabVisible(boolean callTabsVisible, boolean animated) { composeButton.setVisibility(factor > 0 ? View.VISIBLE : View.GONE);
if (tabsView != null) {
tabsView.setViewVisible(tabs[INDEX_SETTINGS], !callTabsVisible, animated);
tabsView.setViewVisible(tabs[INDEX_CALLS], callTabsVisible, animated);
} }
} }
@ -1024,7 +1020,7 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
private boolean accountSwitchHintShown; private boolean accountSwitchHintShown;
private void showAccountChangeHint() { private void showAccountChangeHint() {
if (accountSwitchHintShown || NekoConfig.hideBottomNavigationBar) return; if (accountSwitchHintShown || NekoConfig.isBottomNavigationBarHidden()) return;
if (accountSwitchHint == null && HintsController.Hint.AccountSwitchHint.show()) { if (accountSwitchHint == null && HintsController.Hint.AccountSwitchHint.show()) {
AndroidUtilities.runOnUIThread(() -> { AndroidUtilities.runOnUIThread(() -> {
@ -1088,5 +1084,8 @@ public class MainTabsActivity extends ViewPagerActivity implements NotificationC
tabView.updateColorsLottie(); tabView.updateColorsLottie();
} }
} }
if (composeButton != null) {
composeButton.updateColorsLottie();
}
} }
} }

View file

@ -41,6 +41,42 @@ public class MainTabsLayout extends AnimatedLinearLayout {
this.resourcesProvider = resourcesProvider; this.resourcesProvider = resourcesProvider;
} }
private int minTotalWidthDp = 320;
public void setMinTotalWidthDp(int value) {
this.minTotalWidthDp = value;
requestLayout();
}
private View spacerView;
private View leadingSpacerView;
private boolean fillParentWidth;
private boolean equalTabs; // when true: all tabs share the full width equally (classic style)
public void setSpacerView(View view) {
this.spacerView = view;
}
public void setEqualTabs(boolean equal) {
this.equalTabs = equal;
requestLayout();
}
/**
* Optional spacer placed before the tabs. When set together with the trailing
* {@link #spacerView}, the free space is split evenly between the two so the tab
* group is centered while the trailing content (e.g. compose button) stays at the
* right edge.
*/
public void setLeadingSpacerView(View view) {
this.leadingSpacerView = view;
}
public void setFillParentWidth(boolean fill) {
this.fillParentWidth = fill;
requestLayout();
}
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = MeasureSpec.getSize(widthMeasureSpec); final int width = MeasureSpec.getSize(widthMeasureSpec);
@ -49,8 +85,38 @@ public class MainTabsLayout extends AnimatedLinearLayout {
measureTabTexts(); measureTabTexts();
// Classic equal-width mode: divide full width evenly among visible tabs
if (equalTabs && visibleChildCount > 0) {
final int totalW = width - getPaddingLeft() - getPaddingRight();
final int tabW = totalW / visibleChildCount;
final int remainder = totalW - tabW * visibleChildCount;
setMeasuredDimension(width, height);
int leftPos = 0;
int tabIdx = 0;
for (int a = 0, N = getChildCount(); a < N; a++) {
final View child = getChildAt(a);
if (!isViewVisible(child)) {
tabsWidth[a] = 0;
tabsLeftPos[a] = leftPos;
child.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(tabHeight, MeasureSpec.EXACTLY));
continue;
}
// Give the last tab any leftover pixel from integer division
final int w = (tabIdx == visibleChildCount - 1) ? tabW + remainder : tabW;
tabsWidth[a] = w;
tabsLeftPos[a] = leftPos;
leftPos += w;
tabIdx++;
child.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(tabHeight, MeasureSpec.EXACTLY));
}
calculateTotalSizesAfterMeasure();
return;
}
final int maxTotalWidthForTabs = width - getPaddingLeft() - getPaddingRight(); final int maxTotalWidthForTabs = width - getPaddingLeft() - getPaddingRight();
final int minTotalWidthForTabs = Math.min(dp(320), maxTotalWidthForTabs); final int minTotalWidthForTabs = Math.min(dp(minTotalWidthDp), maxTotalWidthForTabs);
final int tabPadding = dp(16); final int tabPadding = dp(16);
final int minTabTextWidthIfEq = (minTotalWidthForTabs / visibleChildCount) - tabPadding * 2; final int minTabTextWidthIfEq = (minTotalWidthForTabs / visibleChildCount) - tabPadding * 2;
@ -61,7 +127,7 @@ public class MainTabsLayout extends AnimatedLinearLayout {
int totalWeight = 0; int totalWeight = 0;
for (int a = 0, N = getChildCount(); a < N; a++) { for (int a = 0, N = getChildCount(); a < N; a++) {
final View child = getChildAt(a); final View child = getChildAt(a);
if (!isViewVisible(child)) { if (!isViewVisible(child) || child == spacerView || child == leadingSpacerView) {
tabsTextWidth[a] = tabsTextWidthWithMargin[a] = 0; tabsTextWidth[a] = tabsTextWidthWithMargin[a] = 0;
tabsWeight[a] = 0; tabsWeight[a] = 0;
continue; continue;
@ -81,11 +147,13 @@ public class MainTabsLayout extends AnimatedLinearLayout {
if (totalWeight == 0) { if (totalWeight == 0) {
for (int a = 0, N = getChildCount(); a < N; a++) { for (int a = 0, N = getChildCount(); a < N; a++) {
tabsWeight[a] = isViewVisible(getChildAt(a)) ? 1 : 0; final View child = getChildAt(a);
tabsWeight[a] = (isViewVisible(child) && child != spacerView && child != leadingSpacerView) ? 1 : 0;
} }
totalWeight = visibleChildCount; totalWeight = visibleChildCount;
} }
if (!fillParentWidth) {
if (totalWidth > maxTotalWidthForTabs) { if (totalWidth > maxTotalWidthForTabs) {
final float m = maxTotalWidthForTabs / totalWidth; final float m = maxTotalWidthForTabs / totalWidth;
for (int a = 0, N = getChildCount(); a < N; a++) { for (int a = 0, N = getChildCount(); a < N; a++) {
@ -95,42 +163,48 @@ public class MainTabsLayout extends AnimatedLinearLayout {
final float growW = minTotalWidthForTabs - totalWidth; final float growW = minTotalWidthForTabs - totalWidth;
final float growP = growW / totalWeight; final float growP = growW / totalWeight;
//boolean needStage2 = false;
for (int a = 0, N = getChildCount(); a < N; a++) { for (int a = 0, N = getChildCount(); a < N; a++) {
final float maxGrow = maxTabTextWidthIfEq - tabsTextWidthWithMargin[a];
//if (tabsWeight[a] > 0 && growP * tabsWeight[a] > maxGrow) {
// needStage2 = true;
// tabsTextWidthWithMargin[a] = maxTabTextWidthIfEq;
//} else {
tabsTextWidthWithMargin[a] += growP * tabsWeight[a]; tabsTextWidthWithMargin[a] += growP * tabsWeight[a];
//}
} }
/*if (needStage2) {
totalWidth = 0;
for (int a = 0, N = getChildCount(); a < N; a++) {
totalWidth += tabsTextWidthWithMargin[a];
} }
} else {
final float m = minTotalWidthForTabs / totalWidth; // tabs keep their natural width; spacer absorbs the remaining width
if (totalWidth > maxTotalWidthForTabs) {
final float m = maxTotalWidthForTabs / totalWidth;
for (int a = 0, N = getChildCount(); a < N; a++) { for (int a = 0, N = getChildCount(); a < N; a++) {
tabsTextWidthWithMargin[a] *= m; tabsTextWidthWithMargin[a] *= m;
} }
}*/ totalWidth = maxTotalWidthForTabs;
}
} }
final int remainingSpace = (int) Math.max(0, maxTotalWidthForTabs - totalWidth);
// When a leading spacer is present, split the free space evenly before and after the
// tab group so the tabs are centered while trailing content (compose) stays at the right.
final boolean hasLeading = leadingSpacerView != null && isViewVisible(leadingSpacerView);
final int leadingSpace = hasLeading ? remainingSpace / 2 : 0;
final int trailingSpace = remainingSpace - leadingSpace;
int l = 0; int l = 0;
for (int a = 0, N = getChildCount(); a < N; a++) { for (int a = 0, N = getChildCount(); a < N; a++) {
if (!isViewVisible(getChildAt(a))) { final View child = getChildAt(a);
if (!isViewVisible(child)) {
continue; continue;
} }
if (child == leadingSpacerView) {
// Always give leading spacer half the free space (centers the tab group)
tabsWidth[a] = leadingSpace;
} else if (child == spacerView) {
tabsWidth[a] = fillParentWidth ? trailingSpace : leadingSpace;
} else {
tabsWidth[a] = Math.round(tabsTextWidthWithMargin[a]); tabsWidth[a] = Math.round(tabsTextWidthWithMargin[a]);
}
tabsLeftPos[a] = l; tabsLeftPos[a] = l;
l += tabsWidth[a]; l += tabsWidth[a];
} }
setMeasuredDimension(l + getPaddingLeft() + getPaddingRight(), height); final int measuredWidth = fillParentWidth ? (maxTotalWidthForTabs + getPaddingLeft() + getPaddingRight()) : (l + getPaddingLeft() + getPaddingRight());
setMeasuredDimension(measuredWidth, height);
for (int a = 0, N = getChildCount(); a < N; a++) { for (int a = 0, N = getChildCount(); a < N; a++) {
final View child = getChildAt(a); final View child = getChildAt(a);
child.measure( child.measure(
@ -279,9 +353,11 @@ public class MainTabsLayout extends AnimatedLinearLayout {
for (int a = 0, N = getEntriesCount(); a < N; a++) { for (int a = 0, N = getEntriesCount(); a < N; a++) {
final ListAnimator.Entry<Holder> entry = getEntry(a); final ListAnimator.Entry<Holder> entry = getEntry(a);
final float width = entry.getRectF().width(); final float width = entry.getRectF().width();
if (entry.item.view instanceof GlassTabView) {
((GlassTabView) entry.item.view).setVisualWidth(width); ((GlassTabView) entry.item.view).setVisualWidth(width);
} }
} }
}

View file

@ -49,6 +49,7 @@ import org.telegram.messenger.ProxyRotationController;
import org.telegram.messenger.R; import org.telegram.messenger.R;
import org.telegram.messenger.SharedConfig; import org.telegram.messenger.SharedConfig;
import org.telegram.messenger.browser.Browser; import org.telegram.messenger.browser.Browser;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.ConnectionsManager;
import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenu;
@ -83,6 +84,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
private final static boolean IS_PROXY_ROTATION_AVAILABLE = true; private final static boolean IS_PROXY_ROTATION_AVAILABLE = true;
private static final int MENU_DELETE = 0; private static final int MENU_DELETE = 0;
private static final int MENU_SHARE = 1; private static final int MENU_SHARE = 1;
private static final int MENU_REFRESH_SERVERS = 2;
private ListAdapter listAdapter; private ListAdapter listAdapter;
private RecyclerListView listView; private RecyclerListView listView;
@ -113,6 +115,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
private int rotationTimeoutInfoRow; private int rotationTimeoutInfoRow;
private int callsDetailRow; private int callsDetailRow;
private int deleteAllRow; private int deleteAllRow;
private int refreshServersRow;
private int newsShadowRow; private int newsShadowRow;
private int newsStartRow; private int newsStartRow;
@ -403,9 +406,14 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
public void onItemClick(int id) { public void onItemClick(int id) {
if (id == -1) { if (id == -1) {
finishFragment(); finishFragment();
} else if (id == MENU_REFRESH_SERVERS) {
onRefreshServersClicked();
} }
} }
}); });
ActionBarMenu menu = actionBar.createMenu();
menu.addItem(MENU_REFRESH_SERVERS, R.drawable.cloud_sync)
.setContentDescription(getString(R.string.FoxRefreshServers));
listAdapter = new ListAdapter(context); listAdapter = new ListAdapter(context);
@ -566,6 +574,8 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
} }
}); });
showDialog(builder.create()); showDialog(builder.create());
} else if (position == refreshServersRow) {
onRefreshServersClicked();
} else if (position == deleteAllRow) { } else if (position == deleteAllRow) {
AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity());
builder.setMessage(getString(R.string.DeleteAllProxiesConfirm)); builder.setMessage(getString(R.string.DeleteAllProxiesConfirm));
@ -710,6 +720,27 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
return super.onBackPressed(invoked); return super.onBackPressed(invoked);
} }
private void onRefreshServersClicked() {
Context ctx = getParentActivity();
if (ctx == null) return;
// Show a brief loading toast
android.widget.Toast.makeText(ctx,
getString(R.string.FoxRefreshServersLoading),
android.widget.Toast.LENGTH_SHORT).show();
XrayController.refreshServers(ctx, () ->
AndroidUtilities.runOnUIThread(() -> {
// Reload builtin proxies into SharedConfig
SharedConfig.reloadBuiltinProxies();
updateRows(true);
if (listAdapter != null) listAdapter.notifyDataSetChanged();
android.widget.Toast.makeText(
ApplicationLoader.applicationContext,
getString(R.string.FoxRefreshServersDone),
android.widget.Toast.LENGTH_SHORT).show();
})
);
}
private void updateRows(boolean notify) { private void updateRows(boolean notify) {
rowCount = 0; rowCount = 0;
useProxyRow = rowCount++; useProxyRow = rowCount++;
@ -741,10 +772,16 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
// Hide built-in proxies that don't match the current network // Hide built-in proxies that don't match the current network
// (e.g. "WiFi" servers on mobile data and vice versa), but never // (e.g. "WiFi" servers on mobile data and vice versa), but never
// hide the proxy that's currently selected. // hide the proxy that's currently selected.
// Also hide sponsor-only servers for non-sponsors.
boolean isSponsor = tw.nekomimi.nekogram.helpers.SponsorHelper.isCurrentUserSponsor();
for (java.util.Iterator<SharedConfig.ProxyInfo> it = proxyList.iterator(); it.hasNext(); ) { for (java.util.Iterator<SharedConfig.ProxyInfo> it = proxyList.iterator(); it.hasNext(); ) {
SharedConfig.ProxyInfo info = it.next(); SharedConfig.ProxyInfo info = it.next();
if (info != SharedConfig.currentProxy && !XrayController.matchesCurrentNetwork(info)) { if (info != SharedConfig.currentProxy && !XrayController.matchesCurrentNetwork(info)) {
it.remove(); it.remove();
continue;
}
if (info.sponsorOnly && !isSponsor && info != SharedConfig.currentProxy) {
it.remove();
} }
} }
@ -785,6 +822,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
proxyEndRow = -1; proxyEndRow = -1;
} }
proxyAddRow = rowCount++; proxyAddRow = rowCount++;
refreshServersRow = rowCount++;
proxyShadowRow = rowCount++; proxyShadowRow = rowCount++;
newsList.clear(); newsList.clear();
newsList.addAll(ConfigHelper.getNewsForProxy()); newsList.addAll(ConfigHelper.getNewsForProxy());
@ -1022,7 +1060,9 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
TextSettingsCell textCell = (TextSettingsCell) holder.itemView; TextSettingsCell textCell = (TextSettingsCell) holder.itemView;
textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
if (position == proxyAddRow) { if (position == proxyAddRow) {
textCell.setText(getString(R.string.AddProxy), deleteAllRow != -1); textCell.setText(getString(R.string.AddProxy), true);
} else if (position == refreshServersRow) {
textCell.setText(getString(R.string.FoxRefreshServers), deleteAllRow != -1);
} else if (position == deleteAllRow) { } else if (position == deleteAllRow) {
textCell.setTextColor(Theme.getColor(Theme.key_text_RedRegular)); textCell.setTextColor(Theme.getColor(Theme.key_text_RedRegular));
textCell.setText(getString(R.string.DeleteAllProxies), false); textCell.setText(getString(R.string.DeleteAllProxies), false);
@ -1135,7 +1175,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
@Override @Override
public boolean isEnabled(RecyclerView.ViewHolder holder) { public boolean isEnabled(RecyclerView.ViewHolder holder) {
int position = holder.getAdapterPosition(); int position = holder.getAdapterPosition();
return position == useProxyRow || position == rotationRow || position == callsRow || position == proxyAddRow || position == deleteAllRow || position >= proxyStartRow && position < proxyEndRow || position >= newsStartRow && position < newsEndRow; return position == useProxyRow || position == rotationRow || position == callsRow || position == proxyAddRow || position == deleteAllRow || position == refreshServersRow || position >= proxyStartRow && position < proxyEndRow || position >= newsStartRow && position < newsEndRow;
} }
@Override @Override
@ -1215,7 +1255,7 @@ public class ProxyListActivity extends BaseFragment implements NotificationCente
public int getItemViewType(int position) { public int getItemViewType(int position) {
if (position == useProxyShadowRow || position == proxyShadowRow || position == newsShadowRow) { if (position == useProxyShadowRow || position == proxyShadowRow || position == newsShadowRow) {
return VIEW_TYPE_SHADOW; return VIEW_TYPE_SHADOW;
} else if (position == proxyAddRow || position == deleteAllRow) { } else if (position == proxyAddRow || position == deleteAllRow || position == refreshServersRow) {
return VIEW_TYPE_TEXT_SETTING; return VIEW_TYPE_TEXT_SETTING;
} else if (position == useProxyRow || position == rotationRow || position == callsRow) { } else if (position == useProxyRow || position == rotationRow || position == callsRow) {
return VIEW_TYPE_TEXT_CHECK; return VIEW_TYPE_TEXT_CHECK;

View file

@ -1162,6 +1162,12 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter
iconBackground.setColor(iconColorTop, iconColorBottom); iconBackground.setColor(iconColorTop, iconColorBottom);
iconView.setImageResource(icon); iconView.setImageResource(icon);
// In light theme use the original icon color; in dark use white
if (!Theme.isCurrentThemeDark()) {
iconView.setColorFilter(new android.graphics.PorterDuffColorFilter(iconColorBottom, android.graphics.PorterDuff.Mode.SRC_IN));
} else {
iconView.setColorFilter(new android.graphics.PorterDuffColorFilter(0xFFFFFFFF, android.graphics.PorterDuff.Mode.SRC_IN));
}
titleView.setText(title); titleView.setText(title);
subtitleView.setVisibility((twoLines = !TextUtils.isEmpty(subtitle)) ? View.VISIBLE : View.GONE); subtitleView.setVisibility((twoLines = !TextUtils.isEmpty(subtitle)) ? View.VISIBLE : View.GONE);
subtitleView.setText(subtitle); subtitleView.setText(subtitle);
@ -1190,6 +1196,12 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter
} }
public void setColor(int topColor, int bottomColor) { public void setColor(int topColor, int bottomColor) {
// In light theme blend with white for soft pastel look; keep vivid in dark theme
if (!Theme.isCurrentThemeDark()) {
final float mix = 0.45f;
topColor = androidx.core.graphics.ColorUtils.blendARGB(topColor, 0xFFFFFFFF, mix);
bottomColor = androidx.core.graphics.ColorUtils.blendARGB(bottomColor, 0xFFFFFFFF, mix);
}
gradient = new LinearGradient(0, 0, 0, dp(28), new int[] { topColor, bottomColor }, new float[] { 0, 1 }, Shader.TileMode.CLAMP); gradient = new LinearGradient(0, 0, 0, dp(28), new int[] { topColor, bottomColor }, new float[] { 0, 1 }, Shader.TileMode.CLAMP);
paint.setShader(gradient); paint.setShader(gradient);
} }

View file

@ -457,6 +457,10 @@ public class DialogStoriesCell extends FrameLayout implements NotificationCenter
this.menuItemsOffset = menuItemsOffset; this.menuItemsOffset = menuItemsOffset;
} }
public void setExtraLeftInset(int insetPx) {
recyclerListView.setPadding(dp(3) + insetPx, 0, dp(3), 0);
}
public void openStoryForCell(StoryCell cell) { public void openStoryForCell(StoryCell cell) {
openStoryForCell(cell, false); openStoryForCell(cell, false);
} }

View file

@ -404,8 +404,8 @@ public class TopicsFragment extends BaseFragment implements NotificationCenter.N
@Override @Override
public View createView(Context context) { public View createView(Context context) {
additionNavigationBarHeight = parentDialogsActivity != null && parentDialogsActivity.hasMainTabs && !NekoConfig.hideBottomNavigationBar ? dp(DialogsActivity.MAIN_TABS_HEIGHT_WITH_MARGINS) : 0; additionNavigationBarHeight = parentDialogsActivity != null && parentDialogsActivity.hasMainTabs && !NekoConfig.isBottomNavigationBarHidden() ? dp(DialogsActivity.MAIN_TABS_HEIGHT_WITH_MARGINS) : 0;
additionFloatingButtonOffset = parentDialogsActivity != null && parentDialogsActivity.hasMainTabs && !NekoConfig.hideBottomNavigationBar ? dp(DialogsActivity.MAIN_TABS_HEIGHT + DialogsActivity.MAIN_TABS_MARGIN) : 0; additionFloatingButtonOffset = parentDialogsActivity != null && parentDialogsActivity.hasMainTabs && !NekoConfig.isBottomNavigationBarHidden() ? dp(DialogsActivity.MAIN_TABS_HEIGHT + DialogsActivity.MAIN_TABS_MARGIN) : 0;
fragmentView = contentView = new SizeNotifierFrameLayout(context) { fragmentView = contentView = new SizeNotifierFrameLayout(context) {
{ {

View file

@ -178,7 +178,7 @@ public abstract class ViewPagerActivity extends BaseFragment {
@Override @Override
public void clearViews() { public void clearViews() {
if (viewPager != null) { if (viewPager != null) {
initialFragmentPosition = NekoConfig.hideBottomNavigationBar ? 0 : viewPager.getCurrentPosition(); initialFragmentPosition = NekoConfig.isBottomNavigationBarHidden() ? 0 : viewPager.getCurrentPosition();
} }
for (int a = 0, N = fragmentsArr.size(); a < N; a++) { for (int a = 0, N = fragmentsArr.size(); a < N; a++) {
final FragmentState state = fragmentsArr.valueAt(a); final FragmentState state = fragmentsArr.valueAt(a);

View file

@ -147,6 +147,11 @@ public class NekoConfig {
public static boolean hideBottomNavigationBar = false; public static boolean hideBottomNavigationBar = false;
public static boolean bottomFilterTabs = false; public static boolean bottomFilterTabs = false;
public static boolean strokeOnViews = true; public static boolean strokeOnViews = true;
public static boolean centerChatTitle = false;
public static boolean useSystemFont = false;
/** When true: new FoxiGram layout (centered title, avatar right, calls in menu).
* When false: stock Telegram layout. Default true. */
public static boolean useNewLayout = true;
public static boolean shouldNOTTrustMe = false; public static boolean shouldNOTTrustMe = false;
@ -260,6 +265,9 @@ public class NekoConfig {
hideBottomNavigationBar = preferences.getBoolean("hideBottomNavigationBar", false); hideBottomNavigationBar = preferences.getBoolean("hideBottomNavigationBar", false);
bottomFilterTabs = preferences.getBoolean("bottomFilterTabs", false); bottomFilterTabs = preferences.getBoolean("bottomFilterTabs", false);
strokeOnViews = preferences.getBoolean("strokeOnViews", true); strokeOnViews = preferences.getBoolean("strokeOnViews", true);
centerChatTitle = preferences.getBoolean("centerChatTitle", false);
useSystemFont = preferences.getBoolean("useSystemFont", false);
useNewLayout = preferences.getBoolean("useNewLayout", true);
cameraInVideoMessages = preferences.getInt("cameraInVideoMessages", CAMERA_FRONT); cameraInVideoMessages = preferences.getInt("cameraInVideoMessages", CAMERA_FRONT);
LensHelper.checkLensSupportAsync(); LensHelper.checkLensSupportAsync();
@ -451,6 +459,47 @@ public class NekoConfig {
editor.apply(); editor.apply();
} }
/**
* Effective visibility of the custom bottom navigation bar (tabs).
* Only the explicit user toggle hides it; the bar stays visible regardless of the
* new-layout setting (its visual style adapts elsewhere instead of disappearing).
*/
public static boolean isBottomNavigationBarHidden() {
return hideBottomNavigationBar;
}
public static void toggleCenterChatTitle() {
centerChatTitle = !centerChatTitle;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("centerChatTitle", centerChatTitle);
editor.apply();
}
public static void toggleUseNewLayout() {
useNewLayout = !useNewLayout;
// centerChatTitle follows useNewLayout automatically
centerChatTitle = useNewLayout;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("useNewLayout", useNewLayout);
editor.putBoolean("centerChatTitle", centerChatTitle);
editor.apply();
// Invalidate typeface caches so layout rebuilds cleanly
AndroidUtilities.mediumTypeface = null;
}
public static void toggleUseSystemFont() {
useSystemFont = !useSystemFont;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("useSystemFont", useSystemFont);
editor.apply();
// Clear typeface cache so changes take effect immediately
org.telegram.messenger.AndroidUtilities.mediumTypeface = null;
org.telegram.messenger.SharedConfig.useSystemBoldFont = useSystemFont;
}
public static void toggleKeepFormatting() { public static void toggleKeepFormatting() {
keepFormatting = !keepFormatting; keepFormatting = !keepFormatting;
SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekoconfig", Activity.MODE_PRIVATE);

View file

@ -33,13 +33,19 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.NekoConfig;
@ -48,6 +54,35 @@ import tw.nekomimi.nekogram.NekoConfig;
public class EmojiHelper { public class EmojiHelper {
private static final String EMOJI_PACKS_FILE_DIR; private static final String EMOJI_PACKS_FILE_DIR;
public static EmojiPack DEFAULT_PACK = new EmojiPack("Apple", "default", "", "", 0); public static EmojiPack DEFAULT_PACK = new EmojiPack("Apple", "default", "", "", 0);
// Built-in downloadable emoji packs
public static final List<BuiltInEmojiPack> BUILT_IN_PACKS = Collections.unmodifiableList(Arrays.asList(
new BuiltInEmojiPack(
"Noto Emoji",
"noto",
"https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf",
"Apache 2.0"
),
new BuiltInEmojiPack(
"Twemoji",
"twemoji",
"https://github.com/mozilla/twemoji-colr/releases/latest/download/Twemoji.Mozilla.ttf",
"CC-BY 4.0"
),
new BuiltInEmojiPack(
"Fluent Emoji",
"fluent",
"https://github.com/nweiz/fluent-emoji-font/releases/latest/download/FluentEmojiFont.ttf",
"MIT"
),
new BuiltInEmojiPack(
"Blobmoji",
"blobmoji",
"https://github.com/C1710/blobmoji/releases/latest/download/BlobmojiCompat.ttf",
"Apache 2.0"
)
));
private static final Runnable invalidateUiRunnable = () -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.emojiLoaded); private static final Runnable invalidateUiRunnable = () -> NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.emojiLoaded);
private static final String[] previewEmojis = { private static final String[] previewEmojis = {
"\uD83D\uDE00", "\uD83D\uDE00",
@ -473,6 +508,74 @@ public class EmojiHelper {
void onUndo(); void onUndo();
} }
public interface DownloadCallback {
void onProgress(int percent);
void onDone(EmojiPack pack);
void onError(Exception e);
}
/**
* Download a built-in emoji pack from its URL, then install it.
* Calls back on the UI thread.
*/
public void downloadBuiltInPack(BuiltInEmojiPack builtIn, DownloadCallback callback) {
AtomicBoolean cancelled = new AtomicBoolean(false);
new Thread(() -> {
File tmpFile = null;
try {
URL url = new URL(builtIn.url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(15000);
conn.setReadTimeout(60000);
conn.connect();
int fileSize = conn.getContentLength();
tmpFile = File.createTempFile("emoji_dl_", ".ttf", AndroidUtilities.getCacheDir());
try (InputStream in = conn.getInputStream();
FileOutputStream out = new FileOutputStream(tmpFile)) {
byte[] buf = new byte[8 * 1024];
int read;
long total = 0;
int lastPercent = -1;
while ((read = in.read(buf)) != -1) {
if (cancelled.get()) return;
out.write(buf, 0, read);
total += read;
if (fileSize > 0) {
int percent = (int) (total * 100 / fileSize);
if (percent != lastPercent) {
lastPercent = percent;
final int p = percent;
AndroidUtilities.runOnUIThread(() -> callback.onProgress(p));
}
}
}
}
File finalTmpFile = tmpFile;
EmojiPack pack = installEmoji(finalTmpFile, false);
AndroidUtilities.runOnUIThread(() -> callback.onDone(pack));
} catch (Exception e) {
FileLog.e("Emoji download failed", e);
AndroidUtilities.runOnUIThread(() -> callback.onError(e));
} finally {
if (tmpFile != null) tmpFile.delete();
}
}).start();
}
public static class BuiltInEmojiPack {
public final String name;
public final String id;
public final String url;
public final String license;
public BuiltInEmojiPack(String name, String id, String url, String license) {
this.name = name;
this.id = id;
this.url = url;
this.license = license;
}
}
public static class EmojiPack { public static class EmojiPack {
protected String packName; protected String packName;
protected String packId; protected String packId;

View file

@ -0,0 +1,443 @@
package tw.nekomimi.nekogram.helpers;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.Build;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.core.view.LayoutInflaterCompat;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.SharedConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import tw.nekomimi.nekogram.NekoConfig;
/**
* Manages the custom font selection feature.
*
* Fonts are bundled as variable TTF files in assets/fonts/custom/.
* Regular-weight fonts are applied globally via LayoutInflater.Factory2.
* Bold/medium fonts are applied via {@link AndroidUtilities#bold()}.
*/
public class FontHelper {
public static final String FONT_ID_DEFAULT = "default";
public static final String FONT_ID_SYSTEM = "system";
public static final List<FontItem> FONTS = Collections.unmodifiableList(Arrays.asList(
new FontItem(FONT_ID_DEFAULT, "Roboto", null, "Default"),
new FontItem(FONT_ID_SYSTEM, "System font", null, "System"),
new FontItem("opensans", "Open Sans", "fonts/custom/opensans.ttf", "Apache 2.0"),
new FontItem("arimo", "Arimo", "fonts/custom/arimo.ttf", "Apache 2.0"),
new FontItem("notosans", "Noto Sans", "fonts/custom/notosans.ttf", "OFL"),
new FontItem("raleway", "Raleway", "fonts/custom/raleway.ttf", "OFL"),
new FontItem("nunito", "Nunito", "fonts/custom/nunito.ttf", "OFL")
));
private static final String PREF_NAME = "nekofonts";
private static final String PREF_KEY = "selected_font";
private static String selectedFontId;
// Cached regular typeface for the currently active font
private static Typeface cachedRegular;
private static Typeface cachedMedium;
// ------------------------------------------------------------------ init
public static void init() {
SharedPreferences prefs = ApplicationLoader.applicationContext
.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
if (prefs.contains(PREF_KEY)) {
selectedFontId = prefs.getString(PREF_KEY, FONT_ID_DEFAULT);
} else if (NekoConfig.useSystemFont) {
selectedFontId = FONT_ID_SYSTEM;
saveFont(FONT_ID_SYSTEM);
} else {
selectedFontId = FONT_ID_DEFAULT;
}
rebuildCache();
}
// ------------------------------------------------------------------ Activity hook
/**
* Install a LayoutInflater.Factory2 on the given Activity so every TextView
* created from XML gets the currently selected regular typeface applied.
* Call this from Activity.onCreate() BEFORE super.onCreate().
*/
public static void installFactory(Activity activity) {
LayoutInflater inflater = activity.getLayoutInflater();
// Only install if no factory is set yet (AppCompat may set one too)
if (inflater.getFactory2() == null) {
LayoutInflaterCompat.setFactory2(inflater, new FontInflaterFactory(null));
}
}
/** Wrap an existing factory so we chain calls correctly. */
public static void installFactory(Activity activity, LayoutInflater.Factory2 delegate) {
LayoutInflater inflater = activity.getLayoutInflater();
LayoutInflaterCompat.setFactory2(inflater, new FontInflaterFactory(delegate));
}
private static class FontInflaterFactory implements LayoutInflater.Factory2 {
private final LayoutInflater.Factory2 delegate;
FontInflaterFactory(LayoutInflater.Factory2 delegate) { this.delegate = delegate; }
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = delegate != null ? delegate.onCreateView(parent, name, context, attrs) : null;
if (view == null) {
// Let the system create it
try {
if (name.contains(".")) {
view = LayoutInflater.from(context).createView(name, null, attrs);
}
} catch (Exception ignored) {}
}
applyToView(view);
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}
}
/**
* Apply the selected regular typeface to a single view.
* Safe to call with null.
*/
public static void applyToView(View view) {
if (view == null) return;
Typeface regular = cachedRegular;
if (regular == null) return; // default Roboto nothing to do
if (view instanceof TextView) {
TextView tv = (TextView) view;
Typeface current = tv.getTypeface();
if (current != null && current.isBold()) {
// Keep bold style but switch the face
view.post(() -> {
Typeface bold = cachedMedium;
if (bold != null) tv.setTypeface(bold);
});
} else {
tv.setTypeface(regular);
}
}
}
// ------------------------------------------------------------------ API
public static String getSelectedFontId() {
if (selectedFontId == null) init();
return selectedFontId;
}
public static FontItem getSelectedFont() {
String id = getSelectedFontId();
for (FontItem f : FONTS) {
if (f.id.equals(id)) return f;
}
return FONTS.get(0);
}
/** Check whether a custom font file (user-installed) exists for the given ID. */
public static boolean isUserFont(String fontId) {
File f = getUserFontFile(fontId);
return f != null && f.exists();
}
public static File getUserFontFile(String fontId) {
if (fontId == null || FONT_ID_DEFAULT.equals(fontId) || FONT_ID_SYSTEM.equals(fontId)) return null;
File dir = new File(ApplicationLoader.applicationContext.getFilesDir(), "user_fonts");
return new File(dir, fontId + ".ttf");
}
public static void setFont(String fontId) {
selectedFontId = fontId;
saveFont(fontId);
rebuildCache();
}
private static void saveFont(String fontId) {
ApplicationLoader.applicationContext
.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit().putString(PREF_KEY, fontId).apply();
// keep NekoConfig.useSystemFont in sync
boolean isSystem = FONT_ID_SYSTEM.equals(fontId);
if (NekoConfig.useSystemFont != isSystem) {
NekoConfig.useSystemFont = isSystem;
ApplicationLoader.applicationContext
.getSharedPreferences("nekoconfig", Context.MODE_PRIVATE)
.edit().putBoolean("useSystemFont", isSystem).apply();
}
}
/** Install a user-provided TTF file and activate it. Returns the new FontItem. */
public static FontItem installUserFont(File src, String name) throws Exception {
String id = "user_" + name.replaceAll("[^a-zA-Z0-9]", "_").toLowerCase();
File dir = new File(ApplicationLoader.applicationContext.getFilesDir(), "user_fonts");
if (!dir.exists()) dir.mkdirs();
File dest = new File(dir, id + ".ttf");
AndroidUtilities.copyFile(src, dest);
// Validate it can be loaded
Typeface.createFromFile(dest);
return new FontItem(id, name, null, "User font") {
{ userFile = dest; }
};
}
// ------------------------------------------------------------------ Google Fonts download
public interface DownloadFontCallback {
void onProgress(int percent);
void onDone(FontItem font);
void onError(Exception e);
}
/**
* Download a font by family name from Google Fonts CSS API v2.
* The TTF is saved to the user_fonts directory.
*/
public static void downloadFromGoogleFonts(String familyName, DownloadFontCallback callback) {
AtomicBoolean cancelled = new AtomicBoolean(false);
new Thread(() -> {
try {
// 1. Resolve download URL via Google Fonts CSS API
String cssUrl = "https://fonts.googleapis.com/css2?family="
+ familyName.replace(" ", "+") + ":wght@400;700&display=swap";
// Use a desktop User-Agent to get TTF (not woff2)
URL url = new URL(cssUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
conn.connect();
String css;
try (InputStream in = conn.getInputStream()) {
byte[] buf = new byte[65536]; int n;
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
while ((n = in.read(buf)) != -1) baos.write(buf, 0, n);
css = baos.toString("UTF-8");
}
// Extract first TTF or truetype url from CSS
String ttfUrl = extractFontUrl(css);
if (ttfUrl == null) throw new Exception("No TTF URL found in Google Fonts CSS");
// 2. Download the TTF
URL ttfConn = new URL(ttfUrl);
HttpURLConnection dl = (HttpURLConnection) ttfConn.openConnection();
dl.setConnectTimeout(15000);
dl.setReadTimeout(60000);
dl.connect();
int total = dl.getContentLength();
File dir = new File(ApplicationLoader.applicationContext.getFilesDir(), "user_fonts");
if (!dir.exists()) dir.mkdirs();
String safeId = "gf_" + familyName.replace(" ", "_").toLowerCase();
File dest = new File(dir, safeId + ".ttf");
try (InputStream in = dl.getInputStream();
FileOutputStream out = new FileOutputStream(dest)) {
byte[] buf = new byte[8192];
int read;
long downloaded = 0;
int lastPercent = -1;
while ((read = in.read(buf)) != -1) {
if (cancelled.get()) return;
out.write(buf, 0, read);
downloaded += read;
if (total > 0) {
int pct = (int) (downloaded * 100 / total);
if (pct != lastPercent) {
lastPercent = pct;
int finalPct = pct;
AndroidUtilities.runOnUIThread(() -> callback.onProgress(finalPct));
}
}
}
}
// Validate
Typeface.createFromFile(dest);
FontItem item = new FontItem(safeId, familyName, null, "Google Fonts") {
{ userFile = dest; }
};
AndroidUtilities.runOnUIThread(() -> callback.onDone(item));
} catch (Exception e) {
FileLog.e("FontHelper: Google Fonts download failed", e);
AndroidUtilities.runOnUIThread(() -> callback.onError(e));
}
}).start();
}
private static String extractFontUrl(String css) {
// Look for src: url(...) lines containing truetype or .ttf
for (String line : css.split("\n")) {
String trimmed = line.trim();
if (trimmed.startsWith("src:") || trimmed.contains("url(")) {
// Extract URL between url( and )
int start = trimmed.indexOf("url(");
int end = trimmed.indexOf(")", start);
if (start >= 0 && end > start) {
String raw = trimmed.substring(start + 4, end).replace("'", "").replace("\"", "");
if (raw.contains(".ttf") || raw.contains("truetype")) {
return raw;
}
}
}
}
return null;
}
// ------------------------------------------------------------------ typeface building
private static void rebuildCache() {
String id = getSelectedFontId();
// Clear AndroidUtilities caches
AndroidUtilities.mediumTypeface = null;
try {
java.lang.reflect.Field f = AndroidUtilities.class.getDeclaredField("typefaceCache");
f.setAccessible(true);
java.util.Hashtable<?, ?> cache = (java.util.Hashtable<?, ?>) f.get(null);
if (cache != null) cache.clear();
} catch (Exception ignored) {}
boolean isSystem = FONT_ID_SYSTEM.equals(id);
SharedConfig.useSystemBoldFont = isSystem;
cachedRegular = buildRegular(id);
cachedMedium = buildMedium(id);
}
private static Typeface buildRegular(String id) {
if (FONT_ID_DEFAULT.equals(id)) return null;
if (FONT_ID_SYSTEM.equals(id)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) return Typeface.create((Typeface) null, 400, false);
return Typeface.create("sans-serif", Typeface.NORMAL);
}
try {
File userFile = getUserFontFile(id);
if (userFile != null && userFile.exists()) return Typeface.createFromFile(userFile);
// Check built-in asset
FontItem item = findBuiltIn(id);
if (item != null && item.assetPath != null) {
return Typeface.createFromAsset(ApplicationLoader.applicationContext.getAssets(), item.assetPath);
}
// Could be a user font with a different file path stored in FontItem.userFile
for (FontItem f : getUserFonts()) {
if (f.id.equals(id) && f.userFile != null) return Typeface.createFromFile(f.userFile);
}
} catch (Exception e) {
FileLog.e("FontHelper: buildRegular failed for " + id, e);
}
return null;
}
private static Typeface buildMedium(String id) {
if (FONT_ID_DEFAULT.equals(id)) return null;
if (FONT_ID_SYSTEM.equals(id)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) return Typeface.create((Typeface) null, 500, false);
return Typeface.create("sans-serif-medium", Typeface.NORMAL);
}
try {
Typeface base = buildRegular(id);
if (base == null) return null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
File userFile = getUserFontFile(id);
if (userFile != null && userFile.exists()) {
return new Typeface.Builder(userFile).setWeight(700).build();
}
FontItem item = findBuiltIn(id);
if (item != null && item.assetPath != null) {
return new Typeface.Builder(
ApplicationLoader.applicationContext.getAssets(), item.assetPath)
.setWeight(700).build();
}
}
return Typeface.create(base, Typeface.BOLD);
} catch (Exception e) {
FileLog.e("FontHelper: buildMedium failed for " + id, e);
}
return null;
}
private static FontItem findBuiltIn(String id) {
for (FontItem f : FONTS) {
if (f.id.equals(id)) return f;
}
return null;
}
/** Returns any user-installed fonts found in the user_fonts directory. */
public static List<FontItem> getUserFonts() {
java.util.List<FontItem> list = new java.util.ArrayList<>();
File dir = new File(ApplicationLoader.applicationContext.getFilesDir(), "user_fonts");
if (!dir.exists()) return list;
File[] files = dir.listFiles();
if (files == null) return list;
for (File f : files) {
if (f.getName().endsWith(".ttf")) {
String fileId = f.getName().replace(".ttf", "");
String displayName = fileId.replace("gf_", "").replace("user_", "").replace("_", " ");
// Capitalize first letter of each word
String[] words = displayName.split(" ");
StringBuilder sb = new StringBuilder();
for (String w : words) {
if (!w.isEmpty()) {
sb.append(Character.toUpperCase(w.charAt(0))).append(w.substring(1)).append(" ");
}
}
FontItem item = new FontItem(fileId, sb.toString().trim(), null,
fileId.startsWith("gf_") ? "Google Fonts" : "User font") {
{ userFile = f; }
};
list.add(item);
}
}
return list;
}
// ------------------------------------------------------------------ public typeface getters
public static Typeface getRegularTypeface() { return cachedRegular; }
public static Typeface getMediumTypeface() { return cachedMedium; }
// ------------------------------------------------------------------ model
public static class FontItem {
public final String id;
public final String name;
/** Asset path for built-in fonts, or null for user/system fonts. */
public final String assetPath;
public final String license;
/** File for user-installed fonts. */
public File userFile;
public FontItem(String id, String name, String assetPath, String license) {
this.id = id;
this.name = name;
this.assetPath = assetPath;
this.license = license;
}
}
}

View file

@ -29,9 +29,9 @@ import java.util.regex.Pattern;
public class UpdateHelper extends BaseRemoteHelper { public class UpdateHelper extends BaseRemoteHelper {
public static final String UPDATE_METHOD = "check_for_updates"; public static final String UPDATE_METHOD = "check_for_updates";
// GitHub repository that hosts FoxiGram releases. // Gitea repository that hosts FoxiGram releases.
private static final String GITHUB_REPO = "instant992/FoxiGram"; private static final String GITHUB_REPO = "Ghost552/FoxiGram";
private static final String RELEASES_API = "https://api.github.com/repos/" + GITHUB_REPO + "/releases/latest"; private static final String RELEASES_API = "https://git.vpnghost.space/api/v1/repos/" + GITHUB_REPO + "/releases/latest";
private static final Pattern VERSION_PART = Pattern.compile("\\d+"); private static final Pattern VERSION_PART = Pattern.compile("\\d+");
@ -124,7 +124,7 @@ public class UpdateHelper extends BaseRemoteHelper {
URL url = new URL(RELEASES_API); URL url = new URL(RELEASES_API);
connection = (HttpURLConnection) url.openConnection(); connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET"); connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/vnd.github+json"); connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("User-Agent", ApplicationLoader.getApplicationId()); connection.setRequestProperty("User-Agent", ApplicationLoader.getApplicationId());
connection.setConnectTimeout(15000); connection.setConnectTimeout(15000);
connection.setReadTimeout(15000); connection.setReadTimeout(15000);

View file

@ -27,6 +27,7 @@ import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.CheckBox2;
import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.RadialProgressView;
import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.helpers.EmojiHelper; import tw.nekomimi.nekogram.helpers.EmojiHelper;
@ -40,10 +41,22 @@ public class EmojiSetCell extends FrameLayout {
private ImageView optionsButton; private ImageView optionsButton;
private CheckBox2 checkBox; private CheckBox2 checkBox;
// Download button (shown for built-in packs that are not yet installed)
private TextView downloadButton;
// Progress shown while downloading
private RadialProgressView progressView;
private EmojiHelper.EmojiPack pack; private EmojiHelper.EmojiPack pack;
private EmojiHelper.BuiltInEmojiPack builtInPack;
private boolean needDivider; private boolean needDivider;
private final boolean selection; private final boolean selection;
// Download state
public static final int STATE_INSTALLED = 0;
public static final int STATE_NOT_INSTALLED = 1;
public static final int STATE_DOWNLOADING = 2;
private int downloadState = STATE_INSTALLED;
public EmojiSetCell(Context context, boolean selection, Theme.ResourcesProvider resourcesProvider) { public EmojiSetCell(Context context, boolean selection, Theme.ResourcesProvider resourcesProvider) {
super(context); super(context);
this.selection = selection; this.selection = selection;
@ -98,11 +111,35 @@ public class EmojiSetCell extends FrameLayout {
checkBox.setDrawUnchecked(false); checkBox.setDrawUnchecked(false);
checkBox.setDrawBackgroundAsArc(3); checkBox.setDrawBackgroundAsArc(3);
addView(checkBox, LayoutHelper.createFrameRelatively(24, 24, Gravity.START, 34, 30, 0, 0)); addView(checkBox, LayoutHelper.createFrameRelatively(24, 24, Gravity.START, 34, 30, 0, 0));
// Download button shown for built-in packs not yet installed
downloadButton = new TextView(context);
downloadButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
downloadButton.setTypeface(AndroidUtilities.getTypeface(AndroidUtilities.TYPEFACE_ROBOTO_MEDIUM));
downloadButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider));
downloadButton.setBackground(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4),
0x1A000000 & Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider),
Theme.multAlpha(Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider), .15f)));
downloadButton.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(4), AndroidUtilities.dp(8), AndroidUtilities.dp(4));
downloadButton.setGravity(Gravity.CENTER);
downloadButton.setVisibility(GONE);
addView(downloadButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT,
(LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL,
LocaleController.isRTL ? 10 : 0, 0, LocaleController.isRTL ? 0 : 10, 0));
// Progress spinner shown while downloading
progressView = new RadialProgressView(context, resourcesProvider);
progressView.setSize(AndroidUtilities.dp(20));
progressView.setVisibility(GONE);
addView(progressView, LayoutHelper.createFrame(28, 28,
(LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL,
LocaleController.isRTL ? 13 : 0, 0, LocaleController.isRTL ? 0 : 13, 0));
} }
} }
public void setData(EmojiHelper.EmojiPack emojiPackInfo, boolean animated, boolean divider) { public void setData(EmojiHelper.EmojiPack emojiPackInfo, boolean animated, boolean divider) {
needDivider = divider; needDivider = divider;
builtInPack = null;
if (selection) { if (selection) {
textView.setText(emojiPackInfo.getPackName()); textView.setText(emojiPackInfo.getPackName());
pack = emojiPackInfo; pack = emojiPackInfo;
@ -112,6 +149,7 @@ public class EmojiSetCell extends FrameLayout {
valueTextView.setText(LocaleController.getString(R.string.InstalledEmojiSet), animated); valueTextView.setText(LocaleController.getString(R.string.InstalledEmojiSet), animated);
} }
setPackPreview(pack); setPackPreview(pack);
setDownloadState(STATE_INSTALLED, animated);
} else { } else {
textView.setText(LocaleController.getString(R.string.EmojiSets)); textView.setText(LocaleController.getString(R.string.EmojiSets));
if (NekoConfig.useSystemEmoji) { if (NekoConfig.useSystemEmoji) {
@ -128,6 +166,54 @@ public class EmojiSetCell extends FrameLayout {
setWillNotDraw(!divider); setWillNotDraw(!divider);
} }
/**
* Bind a built-in (downloadable) pack that is not yet installed.
*/
public void setBuiltInData(EmojiHelper.BuiltInEmojiPack info, int state, boolean animated, boolean divider) {
needDivider = divider;
pack = null;
builtInPack = info;
textView.setText(info.name);
valueTextView.setText(info.license, animated);
imageView.setImageDrawable(null);
setDownloadState(state, animated);
setWillNotDraw(!divider);
}
public void setDownloadState(int state, boolean animated) {
this.downloadState = state;
if (downloadButton == null || progressView == null) return;
switch (state) {
case STATE_NOT_INSTALLED:
downloadButton.setText(LocaleController.getString(R.string.EmojiPackDownload));
downloadButton.setVisibility(VISIBLE);
progressView.setVisibility(GONE);
break;
case STATE_DOWNLOADING:
downloadButton.setVisibility(GONE);
progressView.setVisibility(VISIBLE);
break;
case STATE_INSTALLED:
default:
downloadButton.setVisibility(GONE);
progressView.setVisibility(GONE);
break;
}
}
public void setDownloadProgress(int percent) {
// RadialProgressView doesn't support determinate progress in all versions;
// just keep it spinning good enough UX for now.
}
public TextView getDownloadButton() {
return downloadButton;
}
public EmojiHelper.BuiltInEmojiPack getBuiltInPack() {
return builtInPack;
}
private void setPackPreview(EmojiHelper.EmojiPack pack) { private void setPackPreview(EmojiHelper.EmojiPack pack) {
if ("default".equals(pack.getPackId())) { if ("default".equals(pack.getPackId())) {
imageView.setImageDrawable(getContext().getDrawable(R.drawable.apple)); imageView.setImageDrawable(getContext().getDrawable(R.drawable.apple));
@ -137,6 +223,7 @@ public class EmojiSetCell extends FrameLayout {
} }
public void setChecked(boolean checked, boolean animated) { public void setChecked(boolean checked, boolean animated) {
if (optionsButton == null) return;
if (animated) { if (animated) {
optionsButton.animate().cancel(); optionsButton.animate().cancel();
optionsButton.animate().setListener(new AnimatorListenerAdapter() { optionsButton.animate().setListener(new AnimatorListenerAdapter() {
@ -163,13 +250,18 @@ public class EmojiSetCell extends FrameLayout {
} }
public void setSelected(boolean selected, boolean animated) { public void setSelected(boolean selected, boolean animated) {
checkBox.setChecked(selected, animated); if (checkBox != null) checkBox.setChecked(selected, animated);
} }
public boolean isChecked() { public boolean isChecked() {
if (optionsButton == null) return false;
return optionsButton.getVisibility() == VISIBLE; return optionsButton.getVisibility() == VISIBLE;
} }
public int getDownloadState() {
return downloadState;
}
@Override @Override
protected void onDraw(@NonNull Canvas canvas) { protected void onDraw(@NonNull Canvas canvas) {
if (needDivider) { if (needDivider) {

View file

@ -0,0 +1,152 @@
package tw.nekomimi.nekogram.settings;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.R;
import org.telegram.messenger.SendMessagesHelper;
import org.telegram.messenger.UserConfig;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.Components.AnimatedFileDrawable;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.UItem;
import org.telegram.ui.Components.UniversalAdapter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import tw.nekomimi.nekogram.helpers.ShimmerHeartDrawable;
public class FoxCloudActivity extends BaseNekoSettingsActivity {
private final int serversRow = rowId++;
private final int sponsorRow = rowId++;
private final int iconsRow = rowId++;
private final int buttonRow = rowId++;
private FrameLayout topView;
private BackupImageView stickerView;
private AnimatedFileDrawable stickerDrawable;
@Override
public View createView(Context context) {
topView = new FrameLayout(context);
stickerView = new BackupImageView(context);
topView.addView(stickerView, LayoutHelper.createFrame(130, 130, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 16, 0, 0));
var titleView = new TextView(context);
titleView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, 22);
titleView.setTypeface(AndroidUtilities.bold());
titleView.setGravity(Gravity.CENTER);
titleView.setText(LocaleController.getString(R.string.FoxCloudTitle));
titleView.setTextColor(0xFF4A90D9);
topView.addView(titleView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 16, 158, 16, 0));
var subtitleView = new TextView(context);
subtitleView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, 14);
subtitleView.setGravity(Gravity.CENTER);
subtitleView.setText(LocaleController.getString(R.string.FoxCloudSubtitle));
subtitleView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText));
topView.addView(subtitleView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 24, 192, 24, 0));
var fragmentView = super.createView(context);
loadSticker();
return fragmentView;
}
private void loadSticker() {
try {
File cacheFile = new File(AndroidUtilities.getCacheDir(), "foxi_cloud.webm");
if (!cacheFile.exists() || cacheFile.length() == 0) {
try (InputStream in = ApplicationLoader.applicationContext.getResources().openRawResource(R.raw.foxi_cloud);
FileOutputStream out = new FileOutputStream(cacheFile)) {
byte[] buffer = new byte[16 * 1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
}
stickerDrawable = new AnimatedFileDrawable(cacheFile, true, 0, 0, null, null, null, 0, UserConfig.selectedAccount, true, 260, 260, null);
stickerView.setImageDrawable(stickerDrawable);
stickerView.getImageReceiver().setAutoRepeat(1);
stickerDrawable.start();
stickerView.setOnClickListener(v -> {
if (stickerDrawable != null) {
stickerDrawable.start();
}
});
} catch (Throwable ignore) {
}
}
@Override
protected boolean needActionBarPadding() {
return false;
}
@Override
protected void fillItems(ArrayList<UItem> items, UniversalAdapter adapter) {
items.add(UItem.asCustomShadow(topView, 240));
items.add(UItem.asButtonSubtext(serversRow, R.drawable.msg2_devices, LocaleController.getString(R.string.FoxPremiumFeatureServersTitle), LocaleController.getString(R.string.FoxPremiumFeatureServersAbout)));
UItem sponsorItem = UItem.asButton(sponsorRow, new ShimmerHeartDrawable(AndroidUtilities.dp(24)), LocaleController.getString(R.string.FoxPremiumFeatureSponsorTitle));
sponsorItem.subtext = LocaleController.getString(R.string.FoxPremiumFeatureSponsorAbout);
items.add(sponsorItem);
items.add(UItem.asButtonSubtext(iconsRow, R.drawable.msg_emoji_smiles, LocaleController.getString(R.string.FoxCloudFeatureIconsTitle), LocaleController.getString(R.string.FoxCloudFeatureIconsAbout)));
items.add(UItem.asShadow(null));
items.add(UItem.asButton(buttonRow, R.drawable.msg_input_like, LocaleController.getString(R.string.FoxPremiumButton)));
items.add(UItem.asShadow(null));
}
@Override
protected void onItemClick(UItem item, View view, int position, float x, float y) {
if (item.id == buttonRow) {
openBotWithCommand("vpnghostbot", "/quickreg");
}
}
private void openBotWithCommand(String username, String command) {
int account = UserConfig.selectedAccount;
MessagesController mc = MessagesController.getInstance(account);
mc.getUserNameResolver().resolve(username, peerId -> {
if (peerId == null || peerId == 0) {
return;
}
AndroidUtilities.runOnUIThread(() -> {
Bundle args = new Bundle();
args.putLong("user_id", peerId);
presentFragment(new ChatActivity(args));
AndroidUtilities.runOnUIThread(() ->
SendMessagesHelper.getInstance(account).sendMessage(
SendMessagesHelper.SendMessageParams.of(command, peerId)), 150);
});
});
}
@Override
protected String getActionBarTitle() {
return LocaleController.getString(R.string.FoxCloudTitle);
}
@Override
public void onFragmentDestroy() {
super.onFragmentDestroy();
if (stickerDrawable != null) {
stickerDrawable.recycle();
stickerDrawable = null;
}
}
}

View file

@ -0,0 +1,152 @@
package tw.nekomimi.nekogram.settings;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.R;
import org.telegram.messenger.SendMessagesHelper;
import org.telegram.messenger.UserConfig;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ChatActivity;
import org.telegram.ui.Components.AnimatedFileDrawable;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.UItem;
import org.telegram.ui.Components.UniversalAdapter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import tw.nekomimi.nekogram.helpers.ShimmerHeartDrawable;
public class FoxSpaceActivity extends BaseNekoSettingsActivity {
private final int serversRow = rowId++;
private final int sponsorRow = rowId++;
private final int iconsRow = rowId++;
private final int buttonRow = rowId++;
private FrameLayout topView;
private BackupImageView stickerView;
private AnimatedFileDrawable stickerDrawable;
@Override
public View createView(Context context) {
topView = new FrameLayout(context);
stickerView = new BackupImageView(context);
topView.addView(stickerView, LayoutHelper.createFrame(130, 130, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 16, 0, 0));
var titleView = new TextView(context);
titleView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, 22);
titleView.setTypeface(AndroidUtilities.bold());
titleView.setGravity(Gravity.CENTER);
titleView.setText(LocaleController.getString(R.string.FoxSpaceTitle));
titleView.setTextColor(0xFF6A5ACD);
topView.addView(titleView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 16, 158, 16, 0));
var subtitleView = new TextView(context);
subtitleView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, 14);
subtitleView.setGravity(Gravity.CENTER);
subtitleView.setText(LocaleController.getString(R.string.FoxSpaceSubtitle));
subtitleView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText));
topView.addView(subtitleView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 24, 192, 24, 0));
var fragmentView = super.createView(context);
loadSticker();
return fragmentView;
}
private void loadSticker() {
try {
File cacheFile = new File(AndroidUtilities.getCacheDir(), "foxi_space.webm");
if (!cacheFile.exists() || cacheFile.length() == 0) {
try (InputStream in = ApplicationLoader.applicationContext.getResources().openRawResource(R.raw.foxi_space);
FileOutputStream out = new FileOutputStream(cacheFile)) {
byte[] buffer = new byte[16 * 1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
}
stickerDrawable = new AnimatedFileDrawable(cacheFile, true, 0, 0, null, null, null, 0, UserConfig.selectedAccount, true, 260, 260, null);
stickerView.setImageDrawable(stickerDrawable);
stickerView.getImageReceiver().setAutoRepeat(1);
stickerDrawable.start();
stickerView.setOnClickListener(v -> {
if (stickerDrawable != null) {
stickerDrawable.start();
}
});
} catch (Throwable ignore) {
}
}
@Override
protected boolean needActionBarPadding() {
return false;
}
@Override
protected void fillItems(ArrayList<UItem> items, UniversalAdapter adapter) {
items.add(UItem.asCustomShadow(topView, 240));
items.add(UItem.asButtonSubtext(serversRow, R.drawable.msg2_devices, LocaleController.getString(R.string.FoxPremiumFeatureServersTitle), LocaleController.getString(R.string.FoxPremiumFeatureServersAbout)));
UItem sponsorItem = UItem.asButton(sponsorRow, new ShimmerHeartDrawable(AndroidUtilities.dp(24)), LocaleController.getString(R.string.FoxPremiumFeatureSponsorTitle));
sponsorItem.subtext = LocaleController.getString(R.string.FoxPremiumFeatureSponsorAbout);
items.add(sponsorItem);
items.add(UItem.asButtonSubtext(iconsRow, R.drawable.msg_emoji_smiles, LocaleController.getString(R.string.FoxSpaceFeatureIconsTitle), LocaleController.getString(R.string.FoxSpaceFeatureIconsAbout)));
items.add(UItem.asShadow(null));
items.add(UItem.asButton(buttonRow, R.drawable.msg_input_like, LocaleController.getString(R.string.FoxPremiumButton)));
items.add(UItem.asShadow(null));
}
@Override
protected void onItemClick(UItem item, View view, int position, float x, float y) {
if (item.id == buttonRow) {
openBotWithCommand("vpnghostbot", "/quickreg");
}
}
private void openBotWithCommand(String username, String command) {
int account = UserConfig.selectedAccount;
MessagesController mc = MessagesController.getInstance(account);
mc.getUserNameResolver().resolve(username, peerId -> {
if (peerId == null || peerId == 0) {
return;
}
AndroidUtilities.runOnUIThread(() -> {
Bundle args = new Bundle();
args.putLong("user_id", peerId);
presentFragment(new ChatActivity(args));
AndroidUtilities.runOnUIThread(() ->
SendMessagesHelper.getInstance(account).sendMessage(
SendMessagesHelper.SendMessageParams.of(command, peerId)), 150);
});
});
}
@Override
protected String getActionBarTitle() {
return LocaleController.getString(R.string.FoxSpaceTitle);
}
@Override
public void onFragmentDestroy() {
super.onFragmentDestroy();
if (stickerDrawable != null) {
stickerDrawable.recycle();
stickerDrawable = null;
}
}
}

View file

@ -19,6 +19,7 @@ import java.util.ArrayList;
import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.helpers.EmojiHelper; import tw.nekomimi.nekogram.helpers.EmojiHelper;
import tw.nekomimi.nekogram.helpers.FontHelper;
import tw.nekomimi.nekogram.helpers.PopupHelper; import tw.nekomimi.nekogram.helpers.PopupHelper;
public class NekoAppearanceSettingsActivity extends BaseNekoSettingsActivity implements NotificationCenter.NotificationCenterDelegate { public class NekoAppearanceSettingsActivity extends BaseNekoSettingsActivity implements NotificationCenter.NotificationCenterDelegate {
@ -39,6 +40,8 @@ public class NekoAppearanceSettingsActivity extends BaseNekoSettingsActivity imp
private final int tabsPositionRow = rowId++; private final int tabsPositionRow = rowId++;
private final int strokeOnViewsRow = rowId++; private final int strokeOnViewsRow = rowId++;
private final int useNewLayoutRow = rowId++;
private final int fontSettingsRow = rowId++;
@Override @Override
public boolean onFragmentCreate() { public boolean onFragmentCreate() {
@ -96,6 +99,11 @@ public class NekoAppearanceSettingsActivity extends BaseNekoSettingsActivity imp
items.add(UItem.asCheck(strokeOnViewsRow, LocaleController.getString(R.string.StrokeOnViews)).setChecked(NekoConfig.strokeOnViews).slug("strokeOnViews")); items.add(UItem.asCheck(strokeOnViewsRow, LocaleController.getString(R.string.StrokeOnViews)).setChecked(NekoConfig.strokeOnViews).slug("strokeOnViews"));
items.add(UItem.asShadow(null)); items.add(UItem.asShadow(null));
items.add(UItem.asHeader(LocaleController.getString(R.string.ChangeChannelNameColor2)));
items.add(UItem.asCheck(useNewLayoutRow, LocaleController.getString(R.string.UseNewLayout)).setChecked(NekoConfig.useNewLayout).slug("useNewLayout"));
items.add(TextSettingsCellFactory.of(fontSettingsRow, LocaleController.getString(R.string.FontSettings), FontHelper.getSelectedFont().name).slug("fontSettings"));
items.add(UItem.asShadow(LocaleController.getString(R.string.UseNewLayoutHint)));
} }
@Override @Override
@ -199,6 +207,14 @@ public class NekoAppearanceSettingsActivity extends BaseNekoSettingsActivity imp
if (view instanceof TextCheckCell) { if (view instanceof TextCheckCell) {
((TextCheckCell) view).setChecked(NekoConfig.strokeOnViews); ((TextCheckCell) view).setChecked(NekoConfig.strokeOnViews);
} }
} else if (id == useNewLayoutRow) {
NekoConfig.toggleUseNewLayout();
if (view instanceof TextCheckCell) {
((TextCheckCell) view).setChecked(NekoConfig.useNewLayout);
}
parentLayout.rebuildAllFragmentViews(false, false);
} else if (id == fontSettingsRow) {
presentFragment(new NekoFontSettingsActivity());
} }
} }

View file

@ -50,6 +50,8 @@ public class NekoDonateActivity extends BaseNekoSettingsActivity {
private final int supportProjectRow = 1; private final int supportProjectRow = 1;
private final int premiumRow = 3; private final int premiumRow = 3;
private final int spaceRow = 4;
private final int cloudRow = 5;
private final int topSponsorsRow = 2; private final int topSponsorsRow = 2;
private final int cryptoRow = 200; private final int cryptoRow = 200;
@ -68,6 +70,16 @@ public class NekoDonateActivity extends BaseNekoSettingsActivity {
UItem premiumItem = UItem.asButton(premiumRow, new ShimmerHeartDrawable(AndroidUtilities.dp(24)), premiumTitle); UItem premiumItem = UItem.asButton(premiumRow, new ShimmerHeartDrawable(AndroidUtilities.dp(24)), premiumTitle);
premiumItem.subtext = LocaleController.getString(R.string.FoxPremiumSubtitle); premiumItem.subtext = LocaleController.getString(R.string.FoxPremiumSubtitle);
items.add(premiumItem); items.add(premiumItem);
SpannableString spaceTitle = new SpannableString(LocaleController.getString(R.string.FoxSpaceTitle));
spaceTitle.setSpan(new ForegroundColorSpan(0xFF6A5ACD), 0, spaceTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
UItem spaceItem = UItem.asButton(spaceRow, new ShimmerHeartDrawable(AndroidUtilities.dp(24)), spaceTitle);
spaceItem.subtext = LocaleController.getString(R.string.FoxSpaceSubtitle);
items.add(spaceItem);
SpannableString cloudTitle = new SpannableString(LocaleController.getString(R.string.FoxCloudTitle));
cloudTitle.setSpan(new ForegroundColorSpan(0xFF4A90D9), 0, cloudTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
UItem cloudItem = UItem.asButton(cloudRow, new ShimmerHeartDrawable(AndroidUtilities.dp(24)), cloudTitle);
cloudItem.subtext = LocaleController.getString(R.string.FoxCloudSubtitle);
items.add(cloudItem);
items.add(UItem.asButtonSubtext(topSponsorsRow, R.drawable.msg_premium_liststar, LocaleController.getString(R.string.FoxTopSponsors), LocaleController.getString(R.string.FoxTopSponsorsAbout))); items.add(UItem.asButtonSubtext(topSponsorsRow, R.drawable.msg_premium_liststar, LocaleController.getString(R.string.FoxTopSponsors), LocaleController.getString(R.string.FoxTopSponsorsAbout)));
items.add(UItem.asShadow(null)); items.add(UItem.asShadow(null));
@ -88,6 +100,10 @@ public class NekoDonateActivity extends BaseNekoSettingsActivity {
openBotWithCommand("vpnghostbot", "/quickreg"); openBotWithCommand("vpnghostbot", "/quickreg");
} else if (id == premiumRow) { } else if (id == premiumRow) {
presentFragment(new FoxPremiumActivity()); presentFragment(new FoxPremiumActivity());
} else if (id == spaceRow) {
presentFragment(new FoxSpaceActivity());
} else if (id == cloudRow) {
presentFragment(new FoxCloudActivity());
} else if (id == topSponsorsRow) { } else if (id == topSponsorsRow) {
presentFragment(new FoxSponsorsActivity()); presentFragment(new FoxSponsorsActivity());
} else if (id >= cryptoRow) { } else if (id >= cryptoRow) {

View file

@ -9,6 +9,7 @@ import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.view.View; import android.view.View;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@ -55,12 +56,17 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
private final ArrayList<EmojiHelper.EmojiPack> emojiPacks = new ArrayList<>(); private final ArrayList<EmojiHelper.EmojiPack> emojiPacks = new ArrayList<>();
private final SparseBooleanArray selectedItems = new SparseBooleanArray(); private final SparseBooleanArray selectedItems = new SparseBooleanArray();
// Download states for built-in packs: key = index in BUILT_IN_PACKS
private final SparseIntArray builtInDownloadStates = new SparseIntArray();
private final int useSystemEmojiRow = rowId++; private final int useSystemEmojiRow = rowId++;
private final int appleRow = rowId++; private final int appleRow = rowId++;
private final int emojiAddRow = rowId++; private final int emojiAddRow = rowId++;
private final int emojiStartRow = 100; private final int emojiStartRow = 100;
// Built-in packs occupy rows 200..203
private final int builtInStartRow = 200;
private ChatAttachAlert chatAttachAlert; private ChatAttachAlert chatAttachAlert;
private NumberTextView selectedCountTextView; private NumberTextView selectedCountTextView;
@ -99,6 +105,23 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
emojiPacks.addAll(EmojiHelper.getInstance().getEmojiPacksInfo()); emojiPacks.addAll(EmojiHelper.getInstance().getEmojiPacksInfo());
} }
/** Returns true if a built-in pack with given id is already installed. */
private boolean isBuiltInInstalled(String builtInId) {
for (EmojiHelper.EmojiPack pack : emojiPacks) {
if (pack.getPackName().equalsIgnoreCase(getBuiltInNameById(builtInId))) {
return true;
}
}
return false;
}
private String getBuiltInNameById(String id) {
for (EmojiHelper.BuiltInEmojiPack p : EmojiHelper.BUILT_IN_PACKS) {
if (p.id.equals(id)) return p.name;
}
return id;
}
@Override @Override
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
protected void fillItems(ArrayList<UItem> items, UniversalAdapter adapter) { protected void fillItems(ArrayList<UItem> items, UniversalAdapter adapter) {
@ -122,6 +145,21 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
CombinedDrawable combinedDrawable = new CombinedDrawable(drawable1, drawable2); CombinedDrawable combinedDrawable = new CombinedDrawable(drawable1, drawable2);
items.add(TextCreationCellFactory.of(emojiAddRow, LocaleController.getString(R.string.AddEmojiSet), combinedDrawable).slug("emojiAdd")); items.add(TextCreationCellFactory.of(emojiAddRow, LocaleController.getString(R.string.AddEmojiSet), combinedDrawable).slug("emojiAdd"));
items.add(UItem.asShadow(LocaleController.getString(R.string.EmojiSetHint))); items.add(UItem.asShadow(LocaleController.getString(R.string.EmojiSetHint)));
// Built-in downloadable packs
items.add(UItem.asHeader(LocaleController.getString(R.string.EmojiPacksAvailable)));
BuiltInEmojiSetCellFactory.DownloadListener downloadListener = this::startBuiltInDownload;
for (int i = 0; i < EmojiHelper.BUILT_IN_PACKS.size(); i++) {
EmojiHelper.BuiltInEmojiPack builtIn = EmojiHelper.BUILT_IN_PACKS.get(i);
int state;
if (builtInDownloadStates.indexOfKey(i) >= 0) {
state = builtInDownloadStates.get(i);
} else {
state = isBuiltInInstalled(builtIn.id) ? EmojiSetCell.STATE_INSTALLED : EmojiSetCell.STATE_NOT_INSTALLED;
}
items.add(BuiltInEmojiSetCellFactory.of(builtInStartRow + i, builtIn, state, downloadListener));
}
items.add(UItem.asShadow(null));
} }
@Override @Override
@ -139,7 +177,7 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
chatAttachAlert.setEmojiPicker(); chatAttachAlert.setEmojiPicker();
chatAttachAlert.init(); chatAttachAlert.init();
chatAttachAlert.show(); chatAttachAlert.show();
} else if (id == appleRow || id >= emojiStartRow) { } else if (id == appleRow || (id >= emojiStartRow && id < builtInStartRow)) {
EmojiSetCell cell = (EmojiSetCell) view; EmojiSetCell cell = (EmojiSetCell) view;
if (id != appleRow && hasSelected()) { if (id != appleRow && hasSelected()) {
toggleSelected(id); toggleSelected(id);
@ -151,19 +189,56 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
EmojiHelper.reloadEmoji(); EmojiHelper.reloadEmoji();
notifyItemChanged(useSystemEmojiRow, PARTIAL); notifyItemChanged(useSystemEmojiRow, PARTIAL);
} }
} else if (id >= builtInStartRow) {
// clicks on the row itself are ignored only the download button triggers download
} }
} }
@Override @Override
protected boolean onItemLongClick(UItem item, View view, int position, float x, float y) { protected boolean onItemLongClick(UItem item, View view, int position, float x, float y) {
var id = item.id; var id = item.id;
if (id >= emojiStartRow) { if (id >= emojiStartRow && id < builtInStartRow) {
toggleSelected(id); toggleSelected(id);
return true; return true;
} }
return super.onItemLongClick(item, view, position, x, y); return super.onItemLongClick(item, view, position, x, y);
} }
private void startBuiltInDownload(int builtInIndex) {
EmojiHelper.BuiltInEmojiPack builtIn = EmojiHelper.BUILT_IN_PACKS.get(builtInIndex);
builtInDownloadStates.put(builtInIndex, EmojiSetCell.STATE_DOWNLOADING);
notifyItemChanged(builtInStartRow + builtInIndex, PARTIAL);
EmojiHelper.getInstance().downloadBuiltInPack(builtIn, new EmojiHelper.DownloadCallback() {
@Override
public void onProgress(int percent) {
// Could update a determinate progress skipped for now
}
@Override
public void onDone(EmojiHelper.EmojiPack pack) {
builtInDownloadStates.put(builtInIndex, EmojiSetCell.STATE_INSTALLED);
listView.adapter.update(true);
EmojiHelper.reloadEmoji();
}
@Override
public void onError(Exception e) {
builtInDownloadStates.put(builtInIndex, EmojiSetCell.STATE_NOT_INSTALLED);
notifyItemChanged(builtInStartRow + builtInIndex, PARTIAL);
if (getParentActivity() != null) {
AndroidUtilities.runOnUIThread(() ->
new AlertDialog.Builder(getParentActivity(), resourcesProvider)
.setTitle(LocaleController.getString(R.string.AppName))
.setMessage(LocaleController.getString(R.string.EmojiPackDownloadError))
.setPositiveButton(LocaleController.getString(R.string.OK), null)
.create().show()
);
}
}
});
}
private void updateEmojiSets() { private void updateEmojiSets() {
var selectedPackId = EmojiHelper.getInstance().getSelectedEmojiPackId(); var selectedPackId = EmojiHelper.getInstance().getSelectedEmojiPackId();
var appleItem = listView.findItemByItemId(appleRow); var appleItem = listView.findItemByItemId(appleRow);
@ -323,7 +398,6 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
processFiles(filesToUpload); processFiles(filesToUpload);
} }
@Override @Override
public void startDocumentSelectActivity() { public void startDocumentSelectActivity() {
try { try {
@ -441,6 +515,8 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
return super.onBackPressed(invoked); return super.onBackPressed(invoked);
} }
// ---- Cell factories ----
private static class EmojiSetCellFactory extends UItem.UItemFactory<EmojiSetCell> { private static class EmojiSetCellFactory extends UItem.UItemFactory<EmojiSetCell> {
static { static {
setup(new EmojiSetCellFactory()); setup(new EmojiSetCellFactory());
@ -471,6 +547,47 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
} }
} }
/** Factory for built-in downloadable packs. */
private static class BuiltInEmojiSetCellFactory extends UItem.UItemFactory<EmojiSetCell> {
static {
setup(new BuiltInEmojiSetCellFactory());
}
interface DownloadListener {
void onDownloadClick(int builtInIndex);
}
@Override
public EmojiSetCell createView(Context context, RecyclerListView listView, int currentAccount, int classGuid, Theme.ResourcesProvider resourcesProvider) {
return new EmojiSetCell(context, true, resourcesProvider);
}
@Override
public void bindView(View view, UItem item, boolean divider, UniversalAdapter adapter, UniversalRecyclerView listView) {
EmojiSetCell cell = (EmojiSetCell) view;
EmojiHelper.BuiltInEmojiPack builtIn = (EmojiHelper.BuiltInEmojiPack) item.object;
int state = item.intValue;
cell.setBuiltInData(builtIn, state, false, divider);
if (cell.getDownloadButton() != null) {
DownloadListener listener = (DownloadListener) item.object2;
cell.getDownloadButton().setOnClickListener(v -> {
if (listener == null) return;
int idx = EmojiHelper.BUILT_IN_PACKS.indexOf(builtIn);
if (idx >= 0) listener.onDownloadClick(idx);
});
}
}
public static UItem of(int id, EmojiHelper.BuiltInEmojiPack pack, int state, DownloadListener listener) {
var item = UItem.ofFactory(BuiltInEmojiSetCellFactory.class);
item.id = id;
item.object = pack;
item.intValue = state;
item.object2 = listener;
return item;
}
}
protected static class TextCreationCellFactory extends UItem.UItemFactory<CreationTextCell> { protected static class TextCreationCellFactory extends UItem.UItemFactory<CreationTextCell> {
static { static {
setup(new TextCreationCellFactory()); setup(new TextCreationCellFactory());
@ -495,5 +612,4 @@ public class NekoEmojiSettingsActivity extends BaseNekoSettingsActivity implemen
return item; return item;
} }
} }
} }

View file

@ -0,0 +1,360 @@
package tw.nekomimi.nekogram.settings;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.messenger.Utilities;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.AnimatedTextView;
import org.telegram.ui.Components.CombinedDrawable;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.RadialProgressView;
import org.telegram.ui.Components.RecyclerListView;
import org.telegram.ui.Components.UItem;
import org.telegram.ui.Components.UniversalAdapter;
import org.telegram.ui.Components.UniversalRecyclerView;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import tw.nekomimi.nekogram.helpers.FontHelper;
public class NekoFontSettingsActivity extends BaseNekoSettingsActivity {
private static final int fontStartRow = 10;
private static final int addFileRow = 900;
private static final int addGFontsRow = 901;
@Override
protected void fillItems(ArrayList<UItem> items, UniversalAdapter adapter) {
items.add(UItem.asHeader(LocaleController.getString(R.string.FontSelectTitle)));
String selectedId = FontHelper.getSelectedFontId();
// Built-in fonts
for (int i = 0; i < FontHelper.FONTS.size(); i++) {
FontHelper.FontItem font = FontHelper.FONTS.get(i);
items.add(FontCellFactory.of(fontStartRow + i, font, font.id.equals(selectedId)));
}
// User-installed fonts
ArrayList<FontHelper.FontItem> userFonts = new ArrayList<>(FontHelper.getUserFonts());
for (int i = 0; i < userFonts.size(); i++) {
FontHelper.FontItem font = userFonts.get(i);
items.add(FontCellFactory.of(fontStartRow + FontHelper.FONTS.size() + i, font, font.id.equals(selectedId)));
}
items.add(UItem.asShadow(null));
// Add font buttons
items.add(UItem.asHeader(LocaleController.getString(R.string.FontAddTitle)));
Drawable addIcon1 = getParentActivity().getDrawable(R.drawable.poll_add_circle);
Drawable addIcon2 = getParentActivity().getDrawable(R.drawable.poll_add_plus);
addIcon1.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_switchTrackChecked), PorterDuff.Mode.MULTIPLY));
addIcon2.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_checkboxCheck), PorterDuff.Mode.MULTIPLY));
CombinedDrawable iconFile = new CombinedDrawable(addIcon1, addIcon2);
Drawable addIcon3 = getParentActivity().getDrawable(R.drawable.poll_add_circle);
Drawable addIcon4 = getParentActivity().getDrawable(R.drawable.poll_add_plus);
addIcon3.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_switchTrackChecked), PorterDuff.Mode.MULTIPLY));
addIcon4.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_checkboxCheck), PorterDuff.Mode.MULTIPLY));
CombinedDrawable iconGF = new CombinedDrawable(addIcon3, addIcon4);
items.add(NekoEmojiSettingsActivity.TextCreationCellFactory.of(addFileRow, LocaleController.getString(R.string.FontAddFile), iconFile));
items.add(NekoEmojiSettingsActivity.TextCreationCellFactory.of(addGFontsRow, LocaleController.getString(R.string.FontAddGoogleFonts), iconGF));
items.add(UItem.asShadow(LocaleController.getString(R.string.FontSelectHint)));
}
@Override
protected void onItemClick(UItem item, View view, int position, float x, float y) {
int id = item.id;
if (id == addFileRow) {
openFilePicker();
return;
}
if (id == addGFontsRow) {
showGoogleFontsDialog();
return;
}
// Font selection
if (id >= fontStartRow) {
int idx = id - fontStartRow;
FontHelper.FontItem font;
if (idx < FontHelper.FONTS.size()) {
font = FontHelper.FONTS.get(idx);
} else {
ArrayList<FontHelper.FontItem> userFonts = new ArrayList<>(FontHelper.getUserFonts());
int userIdx = idx - FontHelper.FONTS.size();
if (userIdx >= userFonts.size()) return;
font = userFonts.get(userIdx);
}
FontHelper.setFont(font.id);
listView.adapter.update(true);
parentLayout.rebuildAllFragmentViews(false, false);
}
}
private void openFilePicker() {
try {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("font/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"font/ttf", "font/otf", "application/octet-stream"});
startActivityForResult(intent, 42);
} catch (Exception e) {
new AlertDialog.Builder(getParentActivity(), resourcesProvider)
.setTitle(LocaleController.getString(R.string.AppName))
.setMessage(e.getMessage())
.setPositiveButton(LocaleController.getString(R.string.OK), null)
.create().show();
}
}
private void showGoogleFontsDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), resourcesProvider);
builder.setTitle(LocaleController.getString(R.string.FontAddGoogleFonts));
builder.setMessage(LocaleController.getString(R.string.FontGoogleFontsHint));
final android.widget.EditText input = new android.widget.EditText(getParentActivity());
input.setHint("Open Sans");
input.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_CAP_WORDS);
int padding = AndroidUtilities.dp(16);
input.setPadding(padding, padding / 2, padding, padding / 2);
builder.setView(input);
builder.setPositiveButton(LocaleController.getString(R.string.EmojiPackDownload), (dialog, which) -> {
String family = input.getText().toString().trim();
if (family.isEmpty()) return;
startGoogleFontsDownload(family);
});
builder.setNegativeButton(LocaleController.getString(R.string.Cancel), null);
builder.create().show();
}
private void startGoogleFontsDownload(String family) {
AlertDialog progress = new AlertDialog(getParentActivity(), 3);
progress.setCanCancel(false);
progress.showDelayed(200);
FontHelper.downloadFromGoogleFonts(family, new FontHelper.DownloadFontCallback() {
@Override
public void onProgress(int percent) {
// progress dialog doesn't show percent, just spinning
}
@Override
public void onDone(FontHelper.FontItem font) {
progress.dismiss();
FontHelper.setFont(font.id);
listView.adapter.update(true);
parentLayout.rebuildAllFragmentViews(false, false);
}
@Override
public void onError(Exception e) {
progress.dismiss();
if (getParentActivity() != null) {
new AlertDialog.Builder(getParentActivity(), resourcesProvider)
.setTitle(LocaleController.getString(R.string.AppName))
.setMessage(LocaleController.getString(R.string.FontDownloadError) + "\n" + e.getMessage())
.setPositiveButton(LocaleController.getString(R.string.OK), null)
.create().show();
}
}
});
}
@Override
public void onActivityResultFragment(int requestCode, int resultCode, Intent data) {
if (requestCode == 42 && resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
AlertDialog progress = new AlertDialog(getParentActivity(), 3);
progress.setCanCancel(false);
progress.showDelayed(200);
Utilities.globalQueue.postRunnable(() -> {
try {
// Copy file to cache
android.net.Uri uri = data.getData();
String fileName = getFileName(uri);
if (fileName == null) fileName = "CustomFont";
String fontName = fileName.replaceAll("\\.[^.]+$", "").replace("-", " ").replace("_", " ");
File cacheFile = new File(AndroidUtilities.getCacheDir(), "font_import.ttf");
try (InputStream is = getParentActivity().getContentResolver().openInputStream(uri);
java.io.FileOutputStream os = new java.io.FileOutputStream(cacheFile)) {
byte[] buf = new byte[8192]; int n;
while ((n = is.read(buf)) != -1) os.write(buf, 0, n);
}
FontHelper.FontItem installed = FontHelper.installUserFont(cacheFile, fontName);
AndroidUtilities.runOnUIThread(() -> {
progress.dismiss();
FontHelper.setFont(installed.id);
listView.adapter.update(true);
parentLayout.rebuildAllFragmentViews(false, false);
});
} catch (Exception e) {
AndroidUtilities.runOnUIThread(() -> {
progress.dismiss();
if (getParentActivity() != null) {
new AlertDialog.Builder(getParentActivity(), resourcesProvider)
.setTitle(LocaleController.getString(R.string.AppName))
.setMessage(e.getMessage())
.setPositiveButton(LocaleController.getString(R.string.OK), null)
.create().show();
}
});
}
});
}
}
private String getFileName(android.net.Uri uri) {
try (android.database.Cursor cursor = getParentActivity().getContentResolver()
.query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
int idx = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME);
if (idx >= 0) return cursor.getString(idx);
}
} catch (Exception ignored) {}
return null;
}
@Override
protected String getActionBarTitle() {
return LocaleController.getString(R.string.FontSettings);
}
@Override
protected String getKey() { return "fonts"; }
// ---- Font cell factory ----
private static class FontCellFactory extends UItem.UItemFactory<FontCell> {
static { setup(new FontCellFactory()); }
@Override
public FontCell createView(Context context, RecyclerListView listView, int currentAccount,
int classGuid, Theme.ResourcesProvider resourcesProvider) {
return new FontCell(context, resourcesProvider);
}
@Override
public void bindView(View view, UItem item, boolean divider, UniversalAdapter adapter,
UniversalRecyclerView listView) {
((FontCell) view).setData((FontHelper.FontItem) item.object, item.checked, divider);
}
public static UItem of(int id, FontHelper.FontItem font, boolean checked) {
UItem item = UItem.ofFactory(FontCellFactory.class);
item.id = id;
item.object = font;
item.checked = checked;
return item;
}
}
@SuppressLint("ViewConstructor")
private static class FontCell extends FrameLayout {
private final TextView nameView;
private final AnimatedTextView subtitleView;
private final ImageView checkView;
private boolean needDivider;
public FontCell(Context context, Theme.ResourcesProvider resourcesProvider) {
super(context);
nameView = new TextView(context);
nameView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
nameView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider));
nameView.setLines(1);
nameView.setMaxLines(1);
nameView.setSingleLine(true);
nameView.setEllipsize(TextUtils.TruncateAt.END);
nameView.setGravity(LayoutHelper.getAbsoluteGravityStart());
addView(nameView, LayoutHelper.createFrameRelatively(
LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT,
Gravity.START, 21, 9, 60, 0));
subtitleView = new AnimatedTextView(context);
subtitleView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText, resourcesProvider));
subtitleView.setAnimationProperties(.55f, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT);
subtitleView.setTextSize(AndroidUtilities.dp(13));
subtitleView.setGravity(LayoutHelper.getAbsoluteGravityStart());
addView(subtitleView, LayoutHelper.createFrameRelatively(
LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT,
Gravity.START, 21, 29, 60, 0));
checkView = new ImageView(context);
checkView.setScaleType(ImageView.ScaleType.CENTER);
checkView.setColorFilter(
Theme.getColor(Theme.key_featuredStickers_addedIcon, resourcesProvider),
PorterDuff.Mode.MULTIPLY);
checkView.setImageResource(R.drawable.floating_check);
checkView.setVisibility(GONE);
addView(checkView, LayoutHelper.createFrame(40, 40,
(LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL,
LocaleController.isRTL ? 10 : 0, 0, LocaleController.isRTL ? 0 : 10, 0));
}
public void setData(FontHelper.FontItem font, boolean checked, boolean divider) {
needDivider = divider;
// Render the name in the font itself
Typeface tf = null;
try {
if (font.userFile != null && font.userFile.exists()) {
tf = Typeface.createFromFile(font.userFile);
} else if (font.assetPath != null) {
tf = Typeface.createFromAsset(getContext().getAssets(), font.assetPath);
}
} catch (Exception ignored) {}
nameView.setTypeface(tf != null ? tf : Typeface.DEFAULT);
nameView.setText(font.name);
subtitleView.setText(font.license, false);
checkView.setVisibility(checked ? VISIBLE : GONE);
setWillNotDraw(!divider);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
if (needDivider) {
canvas.drawLine(
LocaleController.isRTL ? 0 : AndroidUtilities.dp(21),
getHeight() - 1,
getWidth() - (LocaleController.isRTL ? AndroidUtilities.dp(21) : 0),
getHeight() - 1,
Theme.dividerPaint);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(
MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(58) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY));
}
}
}

View file

@ -154,23 +154,23 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
items.add(UItem.asCustomShadow(topView, 200 - 12)); items.add(UItem.asCustomShadow(topView, 200 - 12));
items.add(UItem.asButton(generalRow, R.drawable.msg_media, LocaleController.getString(R.string.General)).slug("general")); items.add(UItem.asButton(generalRow, R.drawable.msg_media, LocaleController.getString(R.string.General)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.BLUE).slug("general"));
items.add(UItem.asButton(appearanceRow, R.drawable.msg_theme, LocaleController.getString(R.string.ChangeChannelNameColor2)).slug("appearance")); items.add(UItem.asButton(appearanceRow, R.drawable.msg_theme, LocaleController.getString(R.string.ChangeChannelNameColor2)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.PURPLE).slug("appearance"));
items.add(UItem.asButton(chatRow, R.drawable.msg_discussion, LocaleController.getString(R.string.Chat)).slug("chat")); items.add(UItem.asButton(chatRow, R.drawable.msg_discussion, LocaleController.getString(R.string.Chat)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.GREEN).slug("chat"));
if (!PasscodeHelper.isSettingsHidden()) { if (!PasscodeHelper.isSettingsHidden()) {
items.add(UItem.asButton(passcodeRow, R.drawable.msg_secret, LocaleController.getString(R.string.PasscodeNeko)).slug("passcode")); items.add(UItem.asButton(passcodeRow, R.drawable.msg_secret, LocaleController.getString(R.string.PasscodeNeko)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.ORANGE).slug("passcode"));
} }
items.add(UItem.asButton(experimentRow, R.drawable.msg_fave, LocaleController.getString(R.string.NotificationsOther)).slug("experiment")); items.add(UItem.asButton(experimentRow, R.drawable.msg_fave, LocaleController.getString(R.string.NotificationsOther)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.RED).slug("experiment"));
AccessibilityManager am = (AccessibilityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACCESSIBILITY_SERVICE); AccessibilityManager am = (AccessibilityManager) ApplicationLoader.applicationContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (am != null && am.isTouchExplorationEnabled()) { if (am != null && am.isTouchExplorationEnabled()) {
items.add(UItem.asButton(accessibilityRow, LocaleController.getString(R.string.AccessibilitySettings)).slug("accessibility")); items.add(UItem.asButton(accessibilityRow, LocaleController.getString(R.string.AccessibilitySettings)).slug("accessibility"));
} }
items.add(UItem.asShadow(null)); items.add(UItem.asShadow(null));
items.add(UItem.asButton(sourceCodeRow, R.drawable.msg_link, LocaleController.getString(R.string.ViewSourceCode), "GitHub").slug("sourceCode")); items.add(UItem.asButton(sourceCodeRow, R.drawable.msg_link, LocaleController.getString(R.string.ViewSourceCode), "GitHub").colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.CYAN).slug("sourceCode"));
items.add(UItem.asButtonSubtext(translationRow, R.drawable.msg_translate, LocaleController.getString(R.string.Translation), LocaleController.getString(R.string.TranslationAbout)).slug("translation")); items.add(UItem.asButtonSubtext(translationRow, R.drawable.msg_translate, LocaleController.getString(R.string.Translation), LocaleController.getString(R.string.TranslationAbout)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.BLUE_DEEP).slug("translation"));
items.add(UItem.asButtonSubtext(donateRow, R.drawable.msg_input_like, LocaleController.getString(R.string.Donate), LocaleController.getString(R.string.DonateAbout)).slug("donate")); items.add(UItem.asButtonSubtext(donateRow, R.drawable.msg_input_like, LocaleController.getString(R.string.Donate), LocaleController.getString(R.string.DonateAbout)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.RED).slug("donate"));
items.add(UItem.asButtonSubtext(checkUpdateRow, R.drawable.msg_reset, LocaleController.getString(R.string.CheckUpdate), UpdateHelper.formatDateUpdate(SharedConfig.lastUpdateCheckTime)).slug("checkUpdate")); items.add(UItem.asButtonSubtext(checkUpdateRow, R.drawable.msg_reset, LocaleController.getString(R.string.CheckUpdate), UpdateHelper.formatDateUpdate(SharedConfig.lastUpdateCheckTime)).colorfulIcon(org.telegram.ui.Components.IconBackgroundColors.GREEN).slug("checkUpdate"));
items.add(UItem.asShadow(null)); items.add(UItem.asShadow(null));
newsList.clear(); newsList.clear();
@ -222,7 +222,7 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
} else if (id == translationRow) { } else if (id == translationRow) {
Browser.openUrl(getParentActivity(), "https://neko.crowdin.com/nekogram"); Browser.openUrl(getParentActivity(), "https://neko.crowdin.com/nekogram");
} else if (id == sourceCodeRow) { } else if (id == sourceCodeRow) {
Browser.openUrl(getParentActivity(), "https://github.com/instant992/FoxiGram"); Browser.openUrl(getParentActivity(), "https://git.vpnghost.space/Ghost552/FoxiGram");
} else if (id >= sponsorRow) { } else if (id >= sponsorRow) {
var news = newsList.get(id - sponsorRow); var news = newsList.get(id - sponsorRow);
Browser.openUrl(getParentActivity(), news.url); Browser.openUrl(getParentActivity(), news.url);
@ -321,7 +321,7 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
})); }));
})); }));
searchResultList.add(new SearchResult(20002, LocaleController.getString(R.string.ViewSourceCode), "GitHub", R.drawable.msg2_help, () -> Browser.openUrl(getParentActivity(), "https://github.com/instant992/FoxiGram"))); searchResultList.add(new SearchResult(20002, LocaleController.getString(R.string.ViewSourceCode), "Gitea", R.drawable.msg2_help, () -> Browser.openUrl(getParentActivity(), "https://git.vpnghost.space/Ghost552/FoxiGram")));
searchResultList.add(new SearchResult(20003, LocaleController.getString(R.string.Translation), LocaleController.getString(R.string.TranslationAbout), R.drawable.msg2_help, () -> Browser.openUrl(getParentActivity(), "https://neko.crowdin.com/nekogram"))); searchResultList.add(new SearchResult(20003, LocaleController.getString(R.string.Translation), LocaleController.getString(R.string.TranslationAbout), R.drawable.msg2_help, () -> Browser.openUrl(getParentActivity(), "https://neko.crowdin.com/nekogram")));
searchResultList.add(new SearchResult(20004, LocaleController.getString(R.string.Donate), LocaleController.getString(R.string.DonateAbout), R.drawable.msg2_help, () -> presentFragment(new NekoDonateActivity()))); searchResultList.add(new SearchResult(20004, LocaleController.getString(R.string.Donate), LocaleController.getString(R.string.DonateAbout), R.drawable.msg2_help, () -> presentFragment(new NekoDonateActivity())));

View file

@ -1,21 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <bitmap xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:src="@mipmap/ic_launcher_foreground"
android:width="108dp" android:gravity="fill" />
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.24942264"
android:scaleY="0.24942264">
<group>
<clip-path
android:pathData="M0.81,0.07h432v432h-432z"
tools:ignore="VectorRaster" />
<path
android:pathData="M513.8,348.07C512.21,423.75 408.56,507.88 330.42,511.95C323.58,511.7 276.4,537.39 231.04,455.79C226.69,447.96 210.06,422.53 201.2,384.37C196,361.96 194.46,345.02 192.58,332.53C190.29,317.31 190.19,316.88 184.83,302.76C175.98,279.44 175.37,276.52 174.13,251.17C173.43,236.95 173.92,216.03 174.97,212.48C175.57,210.54 176.91,207.73 181.71,208.17C189.42,208.89 199.86,214.55 219.87,225.78C234.82,234.17 240.41,237.52 243.52,239.28C246.24,239.3 246.05,238.51 252.15,233.12C264.14,222.52 269.72,217.81 286.39,209.15C290.8,206.87 295.8,205.62 296.19,204.89C296.72,203.91 296.44,200.92 295.07,193.79C290.68,171.07 287.59,143.26 287.51,135.29C287.47,130.94 287.96,129.28 290.18,128.93C290.92,128.81 291.86,128.83 293.03,128.96C297.48,129.43 305.19,132.11 313.73,135.29C326.4,140 341.54,146.87 352.02,155.31C360.15,161.86 368.46,169.02 377.87,173.53C465.3,208.17 526.3,293.07 513.8,348.07ZM324.83,230.07C330.18,230.07 333.25,231.75 336.83,236.57C340.74,242.44 339.41,252.82 335.33,255.57C329.51,259.48 321.42,259.1 315.83,251.57C311.43,244.72 313.83,234.82 317.83,232.07C319.83,230.69 322.92,230.06 324.83,230.07ZM243.83,290.57C249.18,290.56 254.74,293.24 258.33,298.07C262.33,304.07 263.46,314.61 256.88,319.15C250.3,323.7 239.64,322.32 235.44,313.93C231.04,305.13 232.85,296.15 237.33,293.07C238.89,291.99 241.87,290.57 243.83,290.57Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"
tools:targetApi="o" />
</group>
</group>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M2,5C2,4.0572 2,3.5858 2.2929,3.2929C2.5858,3 3.0572,3 4,3H20C20.9428,3 21.4142,3 21.7071,3.2929C22,3.5858 22,4.0572 22,5C22,5.9428 22,6.4142 21.7071,6.7071C21.4142,7 20.9428,7 20,7H4C3.0572,7 2.5858,7 2.2929,6.7071C2,6.4142 2,5.9428 2,5Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M20.0689,8.5C20.2101,8.5 20.3551,8.5 20.5,8.498V13C20.5,16.7712 20.5,18.6569 19.3284,19.8284C18.1569,21 16.2712,21 12.5,21H11.5C7.7288,21 5.8431,21 4.6716,19.8284C3.5,18.6569 3.5,16.7712 3.5,13V8.498C3.6449,8.5 3.79,8.5 3.9311,8.5H20.0689ZM9,12C9,11.5339 9,11.301 9.0761,11.1172C9.1776,10.8722 9.3723,10.6775 9.6173,10.576C9.8011,10.5 10.0341,10.5 10.5,10.5H13.5C13.9659,10.5 14.1989,10.5 14.3827,10.576C14.6277,10.6775 14.8224,10.8722 14.9239,11.1172C15,11.301 15,11.5339 15,12C15,12.4658 15,12.6988 14.9239,12.8826C14.8224,13.1276 14.6277,13.3223 14.3827,13.4238C14.1989,13.5 13.9659,13.5 13.5,13.5H10.5C10.0341,13.5 9.8011,13.5 9.6173,13.4238C9.3723,13.3223 9.1776,13.1276 9.0761,12.8826C9,12.6988 9,12.4658 9,12Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20,11.25C20.4142,11.25 20.75,11.5858 20.75,12C20.75,12.4142 20.4142,12.75 20,12.75H10.75V18C10.75,18.3034 10.5673,18.5768 10.287,18.6929C10.0068,18.809 9.6842,18.7449 9.4697,18.5304L3.4697,12.5304C3.329,12.3897 3.25,12.1989 3.25,12C3.25,11.8011 3.329,11.6103 3.4697,11.4697L9.4697,5.4697C9.6842,5.2552 10.0068,5.191 10.287,5.3071C10.5673,5.4232 10.75,5.6967 10.75,6V11.25H20Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M4,11.25C3.5858,11.25 3.25,11.5858 3.25,12C3.25,12.4142 3.5858,12.75 4,12.75H13.25V18C13.25,18.3034 13.4327,18.5768 13.713,18.6929C13.9932,18.809 14.3158,18.7449 14.5303,18.5304L20.5303,12.5304C20.671,12.3897 20.75,12.1989 20.75,12C20.75,11.8011 20.671,11.6103 20.5303,11.4697L14.5303,5.4697C14.3158,5.2552 13.9932,5.191 13.713,5.3071C13.4327,5.4232 13.25,5.6967 13.25,6V11.25H4Z"/>
</vector>

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M16.5,15.75C14.9812,15.75 13.75,16.9812 13.75,18.5C13.75,19.0004 13.8832,19.4691 14.1167,19.8732L17.8732,16.1167C17.4691,15.8832 17.0004,15.75 16.5,15.75ZM18.9196,17.1916L15.1916,20.9196C15.5806,21.1305 16.0261,21.25 16.5,21.25C18.0188,21.25 19.25,20.0188 19.25,18.5C19.25,18.0261 19.1305,17.5806 18.9196,17.1916ZM12.25,18.5C12.25,16.1528 14.1528,14.25 16.5,14.25C17.689,14.25 18.7652,14.7393 19.5357,15.5256C20.2861,16.2914 20.75,17.3423 20.75,18.5C20.75,20.8472 18.8472,22.75 16.5,22.75C15.3423,22.75 14.2914,22.2861 13.5256,21.5357C12.7393,20.7652 12.25,19.689 12.25,18.5Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M16,6C16,8.2091 14.2091,10 12,10C9.7909,10 8,8.2091 8,6C8,3.7909 9.7909,2 12,2C14.2091,2 16,3.7909 16,6Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M14.2951,13.1879C12.2137,14.0529 10.75,16.1055 10.75,18.5C10.75,19.8163 11.1943,21.0315 11.9378,22C4,21.9895 4,19.9788 4,17.5C4,15.0147 7.5817,13 12,13C12.7977,13 13.5681,13.0657 14.2951,13.1879Z"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M7.75,2.5C7.75,2.0858 7.4142,1.75 7,1.75C6.5858,1.75 6.25,2.0858 6.25,2.5V4.0793C4.8107,4.1945 3.8658,4.4774 3.1716,5.1716C2.4774,5.8658 2.1945,6.8107 2.0793,8.25H21.9207C21.8055,6.8107 21.5226,5.8658 20.8284,5.1716C20.1342,4.4774 19.1893,4.1945 17.75,4.0793V2.5C17.75,2.0858 17.4142,1.75 17,1.75C16.5858,1.75 16.25,2.0858 16.25,2.5V4.0129C15.5847,4 14.839,4 14,4H10C9.161,4 8.4153,4 7.75,4.0129V2.5Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M2,12C2,11.161 2,10.4153 2.0129,9.75H21.9871C22,10.4153 22,11.161 22,12V14C22,17.7712 22,19.6569 20.8284,20.8284C19.6569,22 17.7712,22 14,22H10C6.2288,22 4.3431,22 3.1716,20.8284C2,19.6569 2,17.7712 2,14V12ZM17,14C17.5523,14 18,13.5523 18,13C18,12.4477 17.5523,12 17,12C16.4477,12 16,12.4477 16,13C16,13.5523 16.4477,14 17,14ZM17,18C17.5523,18 18,17.5523 18,17C18,16.4477 17.5523,16 17,16C16.4477,16 16,16.4477 16,17C16,17.5523 16.4477,18 17,18ZM13,13C13,13.5523 12.5523,14 12,14C11.4477,14 11,13.5523 11,13C11,12.4477 11.4477,12 12,12C12.5523,12 13,12.4477 13,13ZM13,17C13,17.5523 12.5523,18 12,18C11.4477,18 11,17.5523 11,17C11,16.4477 11.4477,16 12,16C12.5523,16 13,16.4477 13,17ZM7,14C7.5523,14 8,13.5523 8,13C8,12.4477 7.5523,12 7,12C6.4477,12 6,12.4477 6,13C6,13.5523 6.4477,14 7,14ZM7,18C7.5523,18 8,17.5523 8,17C8,16.4477 7.5523,16 7,16C6.4477,16 6,16.4477 6,17C6,17.5523 6.4477,18 7,18Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.5562,12.9062L16.1007,13.359C16.1007,13.359 15.0181,14.4355 12.0631,11.4972C9.1081,8.559 10.1907,7.4826 10.1907,7.4826L10.4775,7.1974C11.1841,6.4948 11.2507,5.3669 10.6342,4.5435L9.3733,2.8591C8.6103,1.8399 7.136,1.7053 6.2615,2.5748L4.6919,4.1355C4.2582,4.5667 3.9677,5.1256 4.0029,5.7456C4.093,7.3318 4.8107,10.7447 8.8154,14.7266C13.0621,18.9492 17.0468,19.117 18.6763,18.9651C19.1917,18.9171 19.6399,18.6546 20.0011,18.2954L21.4217,16.883C22.3806,15.9295 22.1102,14.2949 20.8833,13.628L18.9728,12.5894C18.1672,12.1515 17.1858,12.2801 16.5562,12.9062Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M22,12C22,17.5228 17.5228,22 12,22C6.4772,22 2,17.5228 2,12C2,6.4772 6.4772,2 12,2C17.5228,2 22,6.4772 22,12ZM8.9696,8.9697C9.2625,8.6768 9.7374,8.6768 10.0303,8.9697L12,10.9393L13.9696,8.9697C14.2625,8.6768 14.7374,8.6768 15.0303,8.9697C15.3232,9.2626 15.3232,9.7374 15.0303,10.0303L13.0606,12L15.0303,13.9696C15.3232,14.2625 15.3232,14.7374 15.0303,15.0303C14.7374,15.3232 14.2625,15.3232 13.9696,15.0303L12,13.0607L10.0303,15.0303C9.7374,15.3232 9.2625,15.3232 8.9697,15.0303C8.6768,14.7374 8.6768,14.2625 8.9697,13.9697L10.9393,12L8.9696,10.0303C8.6767,9.7374 8.6767,9.2625 8.9696,8.9697Z"/>
</vector>

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M9.001,6m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M9.001,17.001c-3.866,0 -7,1.343 -7,3s3.134,4 7,4c3.325,0 6.117,-1.086 6.836,-2.566"/>
<path
android:fillColor="#FF000000"
android:pathData="M20.9996,17.0007C20.9996,18.6576 18.9641,20.0007 16.4788,20.0007C17.211,19.2003 17.7145,18.1958 17.7145,17.0021C17.7145,15.807 17.2098,14.8015 16.4762,14.0007C18.9615,14.0007 20.9996,15.3438 20.9996,17.0007Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M17.9996,6.001C17.9996,7.6578 16.6565,9.001 14.9996,9.001C14.6383,9.001 14.292,8.9371 13.9712,8.8201C14.4443,7.988 14.7145,7.0255 14.7145,5.9999C14.7145,4.975 14.4447,4.0132 13.9722,3.1815C14.2927,3.0647 14.6387,3.001 14.9996,3.001C16.6565,3.001 17.9996,4.3441 17.9996,6.001Z"/>
</vector>

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M9.001,6m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M9.001,17.001c-3.866,0 -7,1.343 -7,3s3.134,4 7,4s7,-1.343 7,-3s-3.134,-4 -7,-4z"/>
<path
android:fillColor="#FF000000"
android:pathData="M20.9996,17.0007C20.9996,18.6576 18.9641,20.0007 16.4788,20.0007C17.211,19.2003 17.7145,18.1958 17.7145,17.0021C17.7145,15.807 17.2098,14.8015 16.4762,14.0007C18.9615,14.0007 20.9996,15.3438 20.9996,17.0007Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M17.9996,6.001C17.9996,7.6578 16.6565,9.001 14.9996,9.001C14.6383,9.001 14.292,8.9371 13.9712,8.8201C14.4443,7.988 14.7145,7.0255 14.7145,5.9999C14.7145,4.975 14.4447,4.0132 13.9722,3.1815C14.2927,3.0647 14.6387,3.001 14.9996,3.001C16.6565,3.001 17.9996,4.3441 17.9996,6.001Z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15.24,2H11.3458C9.5816,2 8.1842,2 7.0905,2.1476C5.965,2.2995 5.054,2.6196 4.3356,3.341C3.6172,4.0623 3.2983,4.9769 3.147,6.107C3,7.205 3,8.608 3,10.3793V16.2169C3,17.725 3.9200,19.0174 5.2272,19.5592C5.1599,18.6498 5.1599,17.3737 5.16,16.312V11.3976C5.1599,10.0207 5.1599,8.9164 5.2783,8.0321C5.4052,7.0844 5.6914,6.1759 6.4253,5.4391C7.1592,4.7022 8.064,4.4149 9.008,4.2874C9.8888,4.1685 10.9887,4.1686 12.2652,4.1687H15.24C15.3348,4.1687 16.6113,4.1685 17.7088,4.1685C18.5896,4.2874 18.0627,2.9478 15.24,2Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M6.6001,11.3974C6.6001,8.6712 6.6001,7.3081 7.4436,6.4612C8.2872,5.6143 9.6448,5.6143 12.3601,5.6143H15.2401C17.9554,5.6143 19.313,5.6143 20.1566,6.4612C21.0001,7.3081 21.0001,8.6712 21.0001,11.3974V16.2167C21.0001,18.9429 21.0001,20.306 20.1566,21.1529C19.313,21.9998 17.9554,21.9998 15.2401,21.9998H12.3601C9.6448,21.9998 8.2872,21.9998 7.4436,21.1529C6.6001,20.306 6.6001,18.9429 6.6001,16.2167V11.3974Z"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3,6.386C3,5.9015 3.3454,5.5088 3.7714,5.5088L6.4357,5.5083C6.965,5.4931 7.432,5.1103 7.6121,4.5441C7.6169,4.5292 7.6223,4.5109 7.6419,4.4442L7.7567,4.0526C7.8269,3.8124 7.8881,3.6032 7.9738,3.4162C8.3121,2.6774 8.9381,2.1643 9.6615,2.033C9.8446,1.9997 10.0385,1.9999 10.2611,2H13.7391C13.9617,1.9999 14.1556,1.9997 14.3387,2.033C15.0621,2.1643 15.6881,2.6774 16.0264,3.4162C16.1121,3.6032 16.1733,3.8124 16.2435,4.0526L16.3583,4.4442C16.3778,4.5109 16.3833,4.5292 16.388,4.5441C16.5682,5.1103 17.1278,4.5088 17.6571,5.5088H20.2286C20.6546,5.5088 21,5.9015 21,6.386C21,6.8704 20.6546,7.2632 20.2286,7.2632H3.7714C3.3454,7.2632 3,6.8704 3,6.386Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M11.5956,22H12.4044C15.1871,22 16.5785,22 17.4831,21.1142C18.3878,20.2283 18.4803,18.7751 18.6654,15.8686L18.9321,11.6807C19.0326,10.1037 19.0828,9.3152 18.6289,8.8156C18.1751,8.3159 17.4087,8.3159 15.876,8.3159H8.124C6.5913,8.3159 5.8249,8.3159 5.3711,8.8156C4.9172,9.3152 4.9674,10.1037 5.0679,11.6807L5.3346,15.8686C5.5197,18.7751 5.6123,20.2283 6.5169,21.1142C7.4215,22 8.8129,22 11.5956,22ZM10.2463,12.1886C10.2051,11.7548 9.8375,11.4382 9.4254,11.4816C9.0132,11.525 8.7125,11.9119 8.7537,12.3457L9.2537,17.6089C9.2949,18.0427 9.6625,18.3593 10.0746,18.3159C10.4868,18.2725 10.7875,17.8856 10.7463,17.4518L10.2463,12.1886ZM14.5746,11.4816C14.9868,11.525 15.2875,11.9119 15.2463,12.3457L14.7463,17.6089C14.7051,18.0427 14.3375,18.3593 13.9254,18.3159C13.5132,18.2725 13.2125,17.8856 13.2537,17.4518L13.7537,12.1886C13.7949,11.7548 14.1625,11.4382 14.5746,11.4816Z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M8.2670,1.6185C8.4778,1.2619 8.9377,1.1437 9.2943,1.3545L10.126,1.8462L19.3731,7.1534C19.7324,7.3596 19.8564,7.8179 19.6503,8.1772C19.4441,8.5364 18.9857,8.6605 18.6264,8.4543L17.7828,7.9701L16.278,10.5675L13.7181,9.0947C13.3591,8.8881 12.9006,9.0117 12.694,9.3707C12.4875,9.7298 12.611,10.1883 12.9701,10.3948L15.526,11.8654L14.5646,13.525L10.3598,11.1057C10.0008,10.8991 9.5423,11.0227 9.3357,11.3818C9.1291,11.7408 9.2527,12.1993 9.6118,12.4059L13.8126,14.8229L12.927,16.3515L10.3125,14.8472C9.9535,14.6407 9.495,14.7643 9.2884,15.1233C9.0818,15.4823 9.2054,15.9408 9.5645,16.1474L12.1751,17.6494L11.0558,19.5814C9.7158,21.8943 6.748,22.6868 4.4271,21.3514C2.1062,20.0161 1.3109,17.0585 2.6509,14.7456L9.3727,3.1433L9.3668,3.1399L8.5309,2.6457C8.1744,2.435 8.0562,1.9751 8.2670,1.6185Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M20,17C21.1046,17 22,16.0672 22,14.9166C22,14.1967 21.217,13.2358 20.6309,12.6174C20.2839,12.2512 19.7161,12.2512 19.3691,12.6174C18.783,13.2358 18,14.1967 18,14.9166C18,16.0672 18.8954,17 20,17Z"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M12,1.25C11.5858,1.25 11.25,1.5858 11.25,2V12.9726L9.5694,11.0119C9.2999,10.6974 8.8264,10.661 8.5119,10.9306C8.1974,11.2001 8.161,11.6736 8.4306,11.9881L11.4306,15.4881C11.573,15.6543 11.7811,15.75 12,15.75C12.2189,15.75 12.427,15.6543 12.5694,15.4881L15.5694,11.9881C15.839,11.6736 15.8026,11.2001 15.4881,10.9306C15.1736,10.661 14.7001,10.6974 14.4306,11.0119L12.75,12.9726V2C12.75,1.5858 12.4142,1.25 12,1.25Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M14.25,9V9.3783C14.9836,9.1197 15.8312,9.2491 16.4642,9.7917C17.4077,10.6004 17.517,12.0208 16.7083,12.9643L13.7083,16.4643C13.2808,16.963 12.6568,17.25 12,17.25C11.3431,17.25 10.7191,16.963 10.2916,16.4643L7.2916,12.9643C6.4829,12.0208 6.5922,10.6004 7.5357,9.7917C8.1687,9.2491 9.0164,9.1197 9.75,9.3783V9H8C5.1716,9 3.7574,9 2.8787,9.8787C2,10.7574 2,12.1716 2,15V16C2,18.8284 2,20.2426 2.8787,21.1213C3.7574,22 5.1716,22 8,22H16C18.8284,22 20.2426,22 21.1213,21.1213C22,20.2426 22,18.8284 22,16V15C22,12.1716 22,10.7574 21.1213,9.8787C20.2426,9 18.8284,9 16,9H14.25Z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M11.4001,18.1612L18.796,10.7653C17.7894,10.3464 16.5972,9.6582 15.4697,8.5307C14.342,7.403 13.6537,6.2106 13.2348,5.2039L5.8388,12.5999C5.2617,13.1771 4.9731,13.4657 4.7249,13.7838C4.4321,14.1592 4.1811,14.5653 3.9763,14.995C3.8027,15.3593 3.6737,15.7465 3.4156,16.5208L2.0545,20.6042C1.9274,20.9852 2.0266,21.4053 2.3106,21.6894C2.5947,21.9734 3.0148,22.0726 3.3958,21.9456L7.4792,20.5844C8.2535,20.3263 8.6407,20.1973 9.005,20.0237C9.4347,19.8189 9.8408,19.5679 10.2162,19.2751C10.5343,19.0269 10.823,18.7383 11.4001,18.1612Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M20.8482,8.7131C22.3839,7.1774 22.3839,4.6875 20.8482,3.1518C19.3125,1.6161 16.8226,1.6161 15.2869,3.1518L14.3999,4.0388C14.4121,4.0755 14.4246,4.1127 14.4377,4.1504C14.7628,5.0875 15.3763,6.316 16.5303,7.47C17.6843,8.624 18.9128,9.2375 19.85,9.5626C19.8875,9.5756 19.9245,9.5882 19.961,9.6003L20.8482,8.7131Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M9.1532,5.4084C10.4198,3.1361 11.0531,2 12,2C12.9469,2 13.5802,3.1361 14.8468,5.4084L15.1745,5.9962C15.5345,6.6419 15.7144,6.9648 15.9951,7.1778C16.2757,7.3908 16.6251,7.4699 17.3241,7.6281L17.9605,7.772C20.4201,8.3286 21.65,8.6068 21.9426,9.5477C22.2352,10.4886 21.3968,11.4691 19.7199,13.4299L19.2861,13.9372C18.8096,14.4944 18.5713,14.773 18.4641,15.1177C18.357,15.4624 18.393,15.8341 18.465,16.5776L18.5306,17.2544C18.7841,19.8706 18.9109,21.1787 18.1449,21.7602C17.3788,22.3417 16.2273,21.8115 13.9243,20.7512L13.3285,20.4768C12.6741,20.1755 12.3469,20.0248 12,20.0248C11.6531,20.0248 11.3259,20.1755 10.6715,20.4768L10.0757,20.7512C7.7727,21.8115 6.6212,22.3417 5.8552,21.7602C5.0891,21.1787 5.2159,19.8706 5.4694,17.2544L5.535,16.5776C5.607,15.8341 5.6431,15.4624 5.5359,15.1177C5.4287,14.773 5.1904,14.4944 4.7139,13.9372L4.2801,13.4299C2.6033,11.4691 1.7648,10.4886 2.0574,9.5477C2.35,8.6068 3.5799,8.3286 6.0395,7.772L6.6759,7.6281C7.3749,7.4699 7.7243,7.3908 8.0049,7.1778C8.2856,6.9648 8.4655,6.6419 8.8255,5.9962L9.1532,5.4084Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M10.0303,6.4697C10.3232,6.7626 10.3232,7.2374 10.0303,7.5303L6.3107,11.25H14.5C15.4534,11.25 16.8667,11.5298 18.0632,12.3913C19.298,13.2804 20.25,14.7556 20.25,17C20.25,17.4142 19.9142,17.75 19.5,17.75C19.0858,17.75 18.75,17.4142 18.75,17C18.75,15.2444 18.0353,14.2196 17.1868,13.6087C16.3,12.9702 15.2133,12.75 14.5,12.75H6.3107L10.0303,16.4697C10.3232,16.7626 10.3232,17.2374 10.0303,17.5303C9.7374,17.8232 9.2626,17.8232 8.9697,17.5303L3.9697,12.5303C3.6768,12.2374 3.6768,11.7626 3.9697,11.4697L8.9697,6.4697C9.2626,6.1768 9.7374,6.1768 10.0303,6.4697Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M22,12C22,17.5228 17.5228,22 12,22C6.4772,22 2,17.5228 2,12C2,6.4772 6.4772,2 12,2C17.5228,2 22,6.4772 22,12ZM12,17.75C12.4142,17.75 12.75,17.4142 12.75,17V11C12.75,10.5858 12.4142,10.25 12,10.25C11.5858,10.25 11.25,10.5858 11.25,11V17C11.25,17.4142 11.5858,17.75 12,17.75ZM12,7C12.5523,7 13,7.4477 13,8C13,8.5523 12.5523,9 12,9C11.4477,9 11,8.5523 11,8C11,7.4477 11.4477,7 12,7Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M2,9.1373C2,14.0003 6.0194,16.5917 8.9617,18.9111C10,19.7296 11,20.5002 12,20.5002C13,20.5002 14,19.7296 15.0383,18.9111C17.9806,16.5917 22,14.0003 22,9.1373C22,4.2744 16.4998,0.8257 12,5.5009C7.5002,0.8257 2,4.2744 2,9.1373Z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15.7284,3.8841C17.1627,2.4442 19.2608,2.414 20.4223,3.5799C21.5859,4.7481 21.5546,6.8593 20.1193,8.3002L17.6955,10.7335C17.4031,11.0269 17.4041,11.5018 17.6975,11.7941C17.991,12.0864 18.4658,12.0855 18.7582,11.7921L21.182,9.3588C23.0933,7.4401 23.3333,4.3768 21.485,2.5213C19.6345,0.6637 16.578,0.9058 14.6657,2.8255L9.8181,7.6919C7.9068,9.6107 7.6668,12.674 9.5151,14.5295C9.8074,14.8229 10.2823,14.8238 10.5757,14.5315C10.8692,14.2392 10.8701,13.7643 10.5778,13.4709C9.4141,12.3027 9.4455,10.1914 10.8808,8.7505L15.7284,3.8841Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M14.485,9.4709C14.1926,9.1774 13.7178,9.1765 13.4243,9.4688C13.1309,9.7611 13.1299,10.236 13.4223,10.5295C14.5859,11.6976 14.5546,13.8089 13.1193,15.2497L8.2717,20.1162C6.8373,21.5561 4.7393,21.5864 3.5778,20.4204C2.4141,19.2522 2.4455,17.1409 3.8808,15.7001L6.3046,13.2668C6.5969,12.9733 6.596,12.4985 6.3026,12.2062C6.0091,11.9138 5.5342,11.9147 5.2419,12.2082L2.8181,14.6415C0.9068,16.5602 0.6668,19.6235 2.5151,21.479C4.3655,23.3367 7.4221,23.0945 9.3344,21.1748L14.182,16.3083C16.0933,14.3896 16.3333,11.3263 14.485,9.4709Z"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M18,8C18,9.1046 17.1046,10 16,10C14.8954,10 14,9.1046 14,8C14,6.8954 14.8954,6 16,6C17.1046,6 18,6.8954 18,8Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M11.9426,1.25H12.0574C14.3658,1.25 16.1748,1.25 17.5863,1.4398C19.031,1.634 20.1711,2.0393 21.0659,2.9341C21.9607,3.829 22.366,4.969 22.5603,6.4137C22.75,7.8252 22.75,9.6342 22.75,11.9426V12.0309C22.75,13.9397 22.75,15.5023 22.6463,16.7745C22.5422,18.0531 22.3287,19.1214 21.8509,20.0087C21.6401,20.4001 21.3812,20.7506 21.0659,21.0659C20.1711,21.9607 19.031,22.366 17.5863,22.5603C16.1748,22.75 14.3658,22.75 12.0574,22.75H11.9426C9.6342,22.75 7.8252,22.75 6.4137,22.5603C4.969,22.366 3.829,21.9607 2.9341,21.0659C2.1409,20.2726 1.7312,19.2852 1.5134,18.0604C1.2994,16.8573 1.2602,15.3603 1.2521,13.5015C1.25,13.0287 1.25,12.5286 1.25,12.001V11.9426C1.25,9.6342 1.25,7.8252 1.4398,6.4137C1.634,4.969 2.0393,3.829 2.9341,2.9341C3.829,2.0393 4.969,1.634 6.4137,1.4398C7.8252,1.25 9.6342,1.25 11.9426,1.25ZM6.6136,2.9264C5.3352,3.0983 4.5645,3.4251 3.9948,3.9948C3.4251,4.5645 3.0983,5.3352 2.9264,6.6136C2.7516,7.9136 2.75,9.6218 2.75,12C2.75,12.2905 2.75,12.5715 2.7503,12.8435L3.7515,11.9675C4.6628,11.1702 6.0362,11.2159 6.8924,12.0721L11.1821,16.3618C11.8693,17.0491 12.9511,17.1428 13.7463,16.5839L14.0445,16.3744C15.1887,15.5702 16.7368,15.6634 17.7764,16.599L20.6068,19.1463C20.8917,18.548 21.0609,17.7618 21.1513,16.6527C21.2494,15.4482 21.25,13.9459 21.25,12C21.25,9.6218 21.2484,7.9136 21.0736,6.6136C20.9018,5.3352 20.5749,4.5645 20.0052,3.9948C19.4355,3.4251 18.6648,3.0983 17.3864,2.9264C16.0864,2.7516 14.3782,2.75 12,2.75C9.6218,2.75 7.9136,2.7516 6.6136,2.9264Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M13.0867,21.3877L13.6288,20.4718C14.0492,19.7614 14.2595,19.4062 14.5972,19.2098C14.9349,19.0134 15.36,19.0061 16.2104,18.9915C17.4658,18.9698 18.2531,18.8929 18.9134,18.6194C20.1386,18.1119 21.1119,17.1386 21.6194,15.9134C22,14.9946 22,13.8297 22,11.5V10.5C22,7.2266 22,5.5899 21.2632,4.3875C20.8509,3.7147 20.2853,3.1491 19.6125,2.7368C18.4101,2 16.7734,2 13.5,2H10.5C7.2266,2 5.5899,2 4.3875,2.7368C3.7147,3.1491 3.1491,3.7147 2.7368,4.3875C2,5.5899 2,7.2266 2,10.5V11.5C2,13.8297 2,14.9946 2.3806,15.9134C2.8881,17.1386 3.8614,18.1119 5.0866,18.6194C5.7469,18.8929 6.5342,18.9698 7.7896,18.9915C8.6399,19.0061 9.0651,19.0134 9.4028,19.2098C9.7405,19.4063 9.9507,19.7614 10.3712,20.4718L10.9133,21.3877C11.3965,22.204 12.6035,22.204 13.0867,21.3877ZM12,6.25C12.4142,6.25 12.75,6.5858 12.75,7V15C12.75,15.4142 12.4142,15.75 12,15.75C11.5858,15.75 11.25,15.4142 11.25,15V7C11.25,6.5858 11.5858,6.25 12,6.25ZM8.75,9C8.75,8.5858 8.4142,8.25 8,8.25C7.5858,8.25 7.25,8.5858 7.25,9V13C7.25,13.4142 7.5858,13.75 8,13.75C8.4142,13.75 8.75,13.4142 8.75,13V9ZM16,8.25C16.4142,8.25 16.75,8.5858 16.75,9V13C16.75,13.4142 16.4142,13.75 16,13.75C15.5858,13.75 15.25,13.4142 15.25,13V9C15.25,8.5858 15.5858,8.25 16,8.25Z"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M8.3518,20.2418C9.1929,21.311 10.5142,22 12,22C13.4858,22 14.8071,21.311 15.6482,20.2419C13.2264,20.57 10.7736,20.57 8.3518,20.2418Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M18.7491,9V9.7041C18.7491,10.5491 18.9903,11.3752 19.4422,12.0782L20.5496,13.8012C21.5612,15.3749 20.789,17.5139 19.0296,18.0116C14.4273,19.3134 9.5727,19.3134 4.9704,18.0116C3.211,17.5139 2.4388,15.3749 3.4504,13.8012L4.5578,12.0782C5.0097,11.3752 5.2509,10.5491 5.2509,9.7041V9C5.2509,5.134 8.2726,2 12,2C15.7274,2 18.7491,5.134 18.7491,9ZM10.0717,9.75C9.6723,9.75 9.3486,9.4142 9.3486,9C9.3486,8.5858 9.6723,8.25 10.0717,8.25H13.9283C14.2208,8.25 14.4845,8.4327 14.5964,8.713C14.7083,8.9932 14.6465,9.3158 14.4397,9.5303L11.8175,12.25H13.9283C14.3277,12.25 14.6515,12.5858 14.6515,13C14.6515,13.4142 14.3277,13.75 13.9283,13.75H10.0717C9.7792,13.75 9.5155,13.5673 9.4036,13.287C9.2917,13.0068 9.3536,12.6842 9.5604,12.4697L12.1826,9.75H10.0717Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.1835,7.8052L16.2188,4.8376C14.1921,2.8089 13.1788,1.7946 12.0904,2.0347C11.0021,2.2748 10.5086,3.6216 9.5217,6.3151L8.8537,8.1381C8.5906,8.8562 8.4591,9.2152 8.2224,9.4929C8.1162,9.6175 7.9954,9.7289 7.8625,9.8245C7.5664,10.0377 7.1981,10.1392 6.4615,10.3423C4.8011,10.8 3.9709,11.0289 3.658,11.5721C3.5228,11.8069 3.4524,12.0735 3.4541,12.3446C3.4581,12.9715 4.067,13.581 5.2848,14.8L6.6994,16.2163L2.2235,20.6964C1.9255,20.9946 1.9255,21.4782 2.2235,21.7764C2.5214,22.0746 3.0044,22.0746 3.3024,21.7764L7.7784,17.2961L9.2444,18.7635C10.4699,19.9902 11.0827,20.6036 11.7134,20.6045C11.9792,20.6049 12.2404,20.5358 12.4713,20.4041C13.0192,20.0914 13.2493,19.2551 13.7095,17.5825C13.9119,16.8472 14.013,16.4795 14.2254,16.1835C14.3184,16.054 14.4262,15.9358 14.5468,15.8314C14.8221,15.593 15.1788,15.459 15.8922,15.191L17.7362,14.4981C20.4,13.4973 21.7319,12.9969 21.9667,11.9115C22.2014,10.826 21.1954,9.8191 19.1835,7.8052Z"/>
</vector>

View file

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M16.5249,2H16.5932C17.477,2 18.1897,2 18.7635,2.0545C19.3552,2.1108 19.8707,2.23 20.3343,2.5141C20.8037,2.8017 21.1983,3.1963 21.486,3.6657C21.77,4.1293 21.8892,4.6448 21.9455,5.2365C22,5.8103 22,6.523 22,7.4068V7.4752C22,8.0559 22,8.5405 21.9626,8.9341C21.9235,9.3456 21.8386,9.7291 21.623,10.0808C21.4121,10.425 21.1227,10.7144 20.7785,10.9254C20.4267,11.1409 20.0433,11.2258 19.6318,11.2649C19.2382,11.3024 18.7535,11.3023 18.1728,11.3023H17.0679C16.2321,11.3024 15.5352,11.3024 14.9819,11.228C14.3979,11.1495 13.8706,10.9768 13.4469,10.5531C13.0232,10.1294 12.8505,9.6021 12.772,9.0181C12.6976,8.4648 12.6976,7.7679 12.6977,6.9321V5.8273C12.6977,5.2465 12.6976,4.7619 12.7351,4.3682C12.7742,3.9567 12.8591,3.5733 13.0746,3.2215C13.2856,2.8773 13.575,2.5879 13.9192,2.377C14.2709,2.1614 14.6544,2.0765 15.0659,2.0374C15.4595,2 15.9442,2 16.5249,2ZM17.3488,7.814C16.8694,7.814 16.6297,7.814 16.4604,7.6939C16.4007,7.6515 16.3485,7.5993 16.3061,7.5396C16.186,7.3703 16.186,7.1306 16.186,6.6512C16.186,6.1717 16.186,5.932 16.3061,5.7627C16.3485,5.703 16.4007,5.6509 16.4604,5.6085C16.6297,5.4884 16.8694,5.4884 17.3488,5.4884C17.8283,5.4884 18.068,5.4884 18.2373,5.6085C18.297,5.6509 18.3491,5.703 18.3915,5.7627C18.5116,5.932 18.5116,6.1717 18.5116,6.6512C18.5116,7.1306 18.5116,7.3703 18.3915,7.5396C18.3491,7.5993 18.297,7.6515 18.2373,7.6939C18.068,7.814 17.8283,7.814 17.3488,7.814Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M10.0808,2.377C9.7291,2.1614 9.3456,2.0765 8.9341,2.0374C8.5405,2 8.0558,2 7.4751,2H7.4068C6.5231,2 5.8103,2 5.2365,2.0545C4.6448,2.1108 4.1293,2.23 3.6657,2.5141C3.1963,2.8017 2.8017,3.1963 2.5141,3.6657C2.23,4.1293 2.1108,4.6448 2.0545,5.2365C2,5.8103 2,6.523 2,7.4068V7.4751C2,8.0558 2,8.5405 2.0374,8.9341C2.0765,9.3456 2.1614,9.7291 2.377,10.0808C2.5879,10.425 2.8773,10.7144 3.2215,10.9254C3.5733,11.1409 3.9567,11.2258 4.3682,11.2649C4.7618,11.3024 5.2464,11.3023 5.8271,11.3023H6.9321C7.7679,11.3024 8.4648,11.3024 9.0181,11.228C9.6021,11.1495 10.1294,10.9768 10.5531,10.5531C10.9768,10.1294 11.1495,9.6021 11.228,9.0181C11.3024,8.4648 11.3024,7.7679 11.3023,6.9321V5.8273C11.3023,5.2466 11.3024,4.7618 11.2649,4.3682C11.2258,3.9567 11.1409,3.5733 10.9254,3.2215C10.7144,2.8773 10.425,2.5879 10.0808,2.377ZM5.7627,7.6939C5.932,7.814 6.1717,7.814 6.6512,7.814C7.1306,7.814 7.3703,7.814 7.5396,7.6939C7.5993,7.6515 7.6515,7.5993 7.6939,7.5396C7.814,7.3703 7.814,7.1306 7.814,6.6512C7.814,6.1717 7.814,5.932 7.6939,5.7627C7.6515,5.703 7.5993,5.6509 7.5396,5.6085C7.3703,5.4884 7.1306,5.4884 6.6512,5.4884C6.1717,5.4884 5.932,5.4884 5.7627,5.6085C5.703,5.6509 5.6509,5.703 5.6085,5.7627C5.4884,5.932 5.4884,6.1717 5.4884,6.6512C5.4884,7.1306 5.4884,7.3703 5.6085,7.5396C5.6509,7.5993 5.703,7.6515 5.7627,7.6939Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M9.0181,12.772C9.6021,12.8505 10.1294,13.0232 10.5531,13.4469C10.9768,13.8706 11.1495,14.3979 11.228,14.9819C11.3024,15.5352 11.3024,16.2321 11.3023,17.0679V18.1728C11.3023,18.7535 11.3024,19.2382 11.2649,19.6318C11.2258,20.0433 11.1409,20.4267 10.9254,20.7785C10.7144,21.1227 10.425,21.4121 10.0808,21.623C9.7291,21.8386 9.3456,21.9235 8.9341,21.9626C8.5405,22 8.0558,22 7.4751,22H7.4068C6.523,22 5.8103,22 5.2365,21.9455C4.6448,21.8892 4.1293,21.77 3.6657,21.486C3.1963,21.1983 2.8017,20.8037 2.5141,20.3343C2.23,19.8707 2.1108,19.3552 2.0545,18.7635C2,18.1897 2,17.477 2,16.5932V16.5249C2,15.9442 2,15.4595 2.0374,15.0659C2.0765,14.6544 2.1614,14.2709 2.377,13.9192C2.5879,13.575 2.8773,13.2856 3.2215,13.0746C3.5733,12.8591 3.9567,12.7742 4.3682,12.7351C4.7618,12.6976 5.2465,12.6977 5.8272,12.6977H6.9321C7.7679,12.6976 8.4648,12.6976 9.0181,12.772ZM6.6512,18.5116C6.1717,18.5116 5.932,18.5116 5.7627,18.3915C5.703,18.3491 5.6509,18.297 5.6085,18.2373C5.4884,18.068 5.4884,17.8283 5.4884,17.3488C5.4884,16.8694 5.4884,16.6297 5.6085,16.4604C5.6509,16.4007 5.703,16.3485 5.7627,16.3061C5.932,16.186 6.1717,16.186 6.6512,16.186C7.1306,16.186 7.3703,16.186 7.5396,16.3061C7.5993,16.3485 7.6515,16.4007 7.6939,16.4604C7.814,16.6297 7.814,16.8694 7.814,17.3488C7.814,17.8283 7.814,18.068 7.6939,18.2373C7.6515,18.297 7.5993,18.3491 7.5396,18.3915C7.3703,18.5116 7.1306,18.5116 6.6512,18.5116Z"/>
<path android:fillColor="#FF000000" android:pathData="M12.6977,16.6155V16.6512H14.093C14.093,15.9834 14.0939,15.5351 14.1286,15.1933C14.1622,14.8632 14.2216,14.7107 14.289,14.6098C14.3738,14.4828 14.4828,14.3738 14.6098,14.289C14.7107,14.2216 14.8632,14.1622 15.1933,14.1286C15.5351,14.0939 15.9834,14.093 16.6512,14.093H18.5116V12.6977H16.6155C15.9926,12.6977 15.4729,12.6976 15.0521,12.7404C14.6117,12.7853 14.203,12.8827 13.8346,13.1288C13.5553,13.3154 13.3154,13.5553 13.1288,13.8346C12.8827,14.203 12.7853,14.6117 12.7404,15.0521C12.6976,15.4729 12.6977,15.9926 12.6977,16.6155Z"/>
<path android:fillColor="#FF000000" android:pathData="M22,18.5351V18.5116H20.6046C20.6046,18.9546 20.6043,19.2519 20.5886,19.4821C20.5733,19.706 20.5459,19.8151 20.5161,19.8868C20.3981,20.1718 20.1718,20.3981 19.8868,20.5161C19.8151,20.5459 19.706,20.5733 19.4821,20.5886C19.2519,20.6043 18.9546,20.6046 18.5116,20.6046H16.6512V22H18.5351C18.9486,22 19.2937,22 19.5771,21.9807C19.8721,21.9606 20.1507,21.9172 20.4208,21.8053C21.0476,21.5456 21.5456,21.0476 21.8053,20.4208C21.9172,20.1507 21.9606,19.8721 21.9807,19.5771C22,19.2937 22,18.9486 22,18.5351Z"/>
<path android:fillColor="#FF000000" android:pathData="M14.093,21.3023C14.093,21.6876 13.7807,22 13.3953,22C13.01,22 12.6977,21.6876 12.6977,21.3023V18.5116H14.093V21.3023Z"/>
<path android:fillColor="#FF000000" android:pathData="M21.3023,12.6977C20.917,12.6977 20.6046,13.01 20.6046,13.3953V16.6512H22V13.3953C22,13.01 21.6876,12.6977 21.3023,12.6977Z"/>
<path android:fillColor="#FF000000" android:pathData="M16.0761,16.6173C16,16.8011 16,17.0341 16,17.5C16,17.9659 16,18.1989 16.0761,18.3827C16.1776,18.6277 16.3723,18.8224 16.6173,18.9239C16.8011,19 17.0341,19 17.5,19C17.9659,19 18.1989,19 18.3827,18.9239C18.6277,18.8224 18.8224,18.6277 18.9239,18.3827C19,18.1989 19,17.9659 19,17.5C19,17.0341 19,16.8011 18.9239,16.6173C18.8224,16.3723 18.6277,16.1776 18.3827,16.0761C18.1989,16 17.9659,16 17.5,16C17.0341,16 16.8011,16 16.6173,16.0761C16.3723,16.1776 16.1776,16.3723 16.0761,16.6173Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M5.75,1C6.1642,1 6.5,1.3358 6.5,1.75V3.6L8.2207,3.2559C9.8712,2.9258 11.5821,3.0828 13.1449,3.708L13.3486,3.7894C14.9097,4.4139 16.628,4.5305 18.2592,4.1227C19.0165,3.9334 19.75,4.5061 19.75,5.2867V12.6537C19.75,13.298 19.3115,13.8596 18.6864,14.0159L18.472,14.0695C16.7024,14.5119 14.8385,14.3854 13.1449,13.708C11.5821,13.0828 9.8712,12.9258 8.2207,13.2559L6.5,13.6V21.75C6.5,22.1642 6.1642,22.5 5.75,22.5C5.3358,22.5 5,22.1642 5,21.75V1.75C5,1.3358 5.3358,1 5.75,1Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M7.5303,3.4697C7.8232,3.7626 7.8232,4.2374 7.5303,4.5303L5.8107,6.25H15C18.1756,6.25 20.75,8.8244 20.75,12C20.75,15.1756 18.1756,17.75 15,17.75H8C7.5858,17.75 7.25,17.4142 7.25,17C7.25,16.5858 7.5858,16.25 8,16.25H15C17.3472,16.25 19.25,14.3472 19.25,12C19.25,9.6528 17.3472,7.75 15,7.75H5.8107L7.5303,9.4697C7.8232,9.7626 7.8232,10.2374 7.5303,10.5303C7.2374,10.8232 6.7626,10.8232 6.4697,10.5303L3.4697,7.5303C3.1768,7.2374 3.1768,6.7626 3.4697,6.4697L6.4697,3.4697C6.7626,3.1768 7.2374,3.1768 7.5303,3.4697Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M21.7883,21.7883C22.0706,21.506 22.0706,21.0483 21.7883,20.7659L18.1224,17.1002C19.4884,15.5007 20.3133,13.425 20.3133,11.1566C20.3133,6.0996 16.2137,2 11.1566,2C6.0996,2 2,6.0996 2,11.1566C2,16.2137 6.0996,20.3133 11.1566,20.3133C13.4249,20.3133 15.5006,19.4885 17.1,18.1225L20.7659,21.7883C21.0483,22.0706 21.506,22.0706 21.7883,21.7883Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M5.25,10.0546V8C5.25,4.2721 8.2721,1.25 12,1.25C15.7279,1.25 18.75,4.2721 18.75,8V10.0546C19.8648,10.1379 20.5907,10.348 21.1213,10.8787C22,11.7574 22,13.1716 22,16C22,18.8284 22,20.2426 21.1213,21.1213C20.2426,22 18.8284,22 16,22H8C5.1716,22 3.7574,22 2.8787,21.1213C2,20.2426 2,18.8284 2,16C2,13.1716 2,11.7574 2.8787,10.8787C3.4093,10.348 4.1353,10.1379 5.25,10.0546ZM6.75,8C6.75,5.1005 9.1005,2.75 12,2.75C14.8995,2.75 17.25,5.1005 17.25,8V10.0036C16.867,10 16.4515,10 16,10H8C7.5485,10 7.133,10 6.75,10.0036V8ZM14,16C14,17.1046 13.1046,18 12,18C10.8954,18 10,17.1046 10,16C10,14.8954 10.8954,14 12,14C13.1046,14 14,14.8954 14,16Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M14.2788,2.1522C13.9085,2 13.439,2 12.5,2C11.561,2 11.0915,2 10.7212,2.1522C10.2274,2.3552 9.8351,2.7446 9.6306,3.2346C9.5372,3.4583 9.5007,3.7185 9.4864,4.098C9.4653,4.6557 9.1772,5.1719 8.6902,5.4509C8.2032,5.73 7.6086,5.7195 7.1115,5.4588C6.7732,5.2813 6.5279,5.1826 6.286,5.151C5.7561,5.0818 5.2202,5.2243 4.7962,5.5472C4.4781,5.7894 4.2434,6.1929 3.7739,6.9999C3.3044,7.807 3.0697,8.2105 3.0174,8.6049C2.9476,9.1308 3.0912,9.6627 3.4166,10.0835C3.5651,10.2756 3.7738,10.437 4.0977,10.639C4.5739,10.936 4.8803,11.4419 4.8803,12C4.8803,12.5581 4.5739,13.0639 4.0977,13.3608C3.7737,13.5629 3.565,13.7244 3.4165,13.9165C3.0911,14.3373 2.9475,14.8691 3.0173,15.395C3.0696,15.7894 3.3043,16.193 3.7738,17C4.2433,17.807 4.478,18.2106 4.7961,18.4527C5.2201,18.7756 5.756,18.9181 6.2859,18.8489C6.5278,18.8173 6.773,18.7186 7.1113,18.5412C7.6085,18.2804 8.2031,18.27 8.6901,18.549C9.1771,18.8281 9.4653,19.3443 9.4864,19.9021C9.5007,20.2815 9.5372,20.5417 9.6306,20.7654C9.8351,21.2554 10.2274,21.6448 10.7212,21.8478C11.0915,22 11.561,22 12.5,22C13.439,22 13.9085,22 14.2788,21.8478C14.7726,21.6448 15.1649,21.2554 15.3694,20.7654C15.4628,20.5417 15.4994,20.2815 15.5137,19.902C15.5347,19.3443 15.8228,18.8281 16.3098,18.549C16.7968,18.2699 17.3914,18.2804 17.8886,18.5412C18.2269,18.7186 18.4721,18.8172 18.714,18.8488C19.2439,18.9181 19.7798,18.7756 20.2038,18.4527C20.5219,18.2105 20.7566,17.807 21.2261,16.9999C21.6956,16.1929 21.9303,15.7894 21.9827,15.395C22.0524,14.8691 21.9088,14.3372 21.5835,13.9164C21.4349,13.7243 21.2262,13.5628 20.9022,13.3608C20.4261,13.0639 20.1197,12.558 20.1197,11.9999C20.1197,11.4418 20.4261,10.9361 20.9022,10.6392C21.2263,10.4371 21.435,10.2757 21.5836,10.0835C21.9089,9.6627 22.0525,9.1309 21.9828,8.605C21.9304,8.2106 21.6957,7.807 21.2262,7C20.7567,6.193 20.522,5.7895 20.2039,5.5473C19.7799,5.2244 19.244,5.0819 18.7141,5.1511C18.4722,5.1827 18.2269,5.2814 17.8887,5.4588C17.3915,5.7196 16.7969,5.73 16.3099,5.451C15.8229,5.1719 15.5347,4.6557 15.5136,4.0979C15.4993,3.7185 15.4628,3.4583 15.3694,3.2346C15.1649,2.7446 14.7726,2.3552 14.2788,2.1522ZM12.5,15C14.1695,15 15.5228,13.6569 15.5228,12C15.5228,10.3431 14.1695,9 12.5,9C10.8305,9 9.4772,10.3431 9.4772,12C9.4772,13.6569 10.8305,15 12.5,15Z"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M8.8447,7.9053C9.1376,8.1982 9.6124,8.1982 9.9053,7.9053L11.625,6.1857V14.375C11.625,14.7892 11.9608,15.125 12.375,15.125C12.7892,15.125 13.125,14.7892 13.125,14.375V6.1857L14.8447,7.9053C15.1376,8.1982 15.6124,8.1982 15.9053,7.9053C16.1982,7.6124 16.1982,7.1376 15.9053,6.8447L12.9053,3.8447C12.6124,3.5518 12.1376,3.5518 11.8447,3.8447L8.8447,6.8447C8.5518,7.1376 8.5518,7.6124 8.8447,7.9053Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M12.375,20.375C16.7933,20.375 20.375,16.7933 20.375,12.375H16.625C15.6822,12.375 15.2108,12.375 14.9179,12.6679C14.625,12.9608 14.625,13.4322 14.625,14.375C14.625,15.6176 13.6176,16.625 12.375,16.625C11.1324,16.625 10.125,15.6176 10.125,14.375C10.125,13.4322 10.125,12.9608 9.8321,12.6679C9.5392,12.375 9.0678,12.375 8.125,12.375H4.375C4.375,16.7933 7.9567,20.375 12.375,20.375Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M13.9697,6.4697C14.2626,6.1768 14.7374,6.1768 15.0303,6.4697L20.0303,11.4697C20.3232,11.7626 20.3232,12.2374 20.0303,12.5303L15.0303,17.5303C14.7374,17.8232 14.2626,17.8232 13.9697,17.5303C13.6768,17.2374 13.6768,16.7626 13.9697,16.4697L17.6893,12.75H9.5C8.7867,12.75 7.7,12.9702 6.8132,13.6087C5.9647,14.2196 5.25,15.2444 5.25,17C5.25,17.4142 4.9142,17.75 4.5,17.75C4.0858,17.75 3.75,17.4142 3.75,17C3.75,14.7556 4.702,13.2804 5.9368,12.3913C7.1333,11.5298 8.5467,11.25 9.5,11.25H17.6893L13.9697,7.5303C13.6768,7.2374 13.6768,6.7626 13.9697,6.4697Z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M3.4645,3.4645C2,4.9289 2,7.286 2,12C2,16.714 2,19.0711 3.4645,20.5355C4.9289,22 7.286,22 12,22C16.714,22 19.0711,22 20.5355,20.5355C22,19.0711 22,16.714 22,12C22,7.286 22,4.9289 20.5355,3.4645C19.0711,2 16.714,2 12,2C7.286,2 4.9289,2 3.4645,3.4645ZM17.5762,10.4801C17.8413,10.1619 17.7983,9.689 17.4801,9.4238C17.1619,9.1587 16.689,9.2017 16.4238,9.5199L14.6269,11.6761C14.2562,12.1211 14.0284,12.3915 13.8409,12.5609C13.7539,12.6394 13.7023,12.6708 13.6775,12.6827C13.6667,12.6875 13.659,12.6842 13.6558,12.6827C13.6311,12.6708 13.5795,12.6394 13.4925,12.5609C13.3049,12.3915 13.0772,12.1211 12.7064,11.6761L12.414,11.3252C12.0855,10.931 11.7894,10.5756 11.5128,10.3258C11.2119,10.0541 10.8328,9.8121 10.3333,9.8121C9.8338,9.8121 9.4548,10.0541 9.1538,10.3258C8.8773,10.5756 8.5811,10.931 8.2527,11.3253L6.4238,13.5199C6.1587,13.8381 6.2017,14.311 6.5199,14.5762C6.8381,14.8413 7.311,14.7983 7.5762,14.4801L9.3731,12.3239C9.7439,11.8789 9.9716,11.6085 10.1591,11.4391C10.2461,11.3606 10.2977,11.3292 10.3225,11.3173C10.3333,11.3125 10.3356,11.3134 10.3442,11.3173C10.3689,11.3292 10.4205,11.3606 10.5075,11.4391C10.6951,11.6085 10.9228,11.8789 11.2936,12.3239L11.586,12.6748C11.9145,13.069 12.2106,13.4244 12.4872,13.6742C12.7881,13.9459 13.1672,14.188 13.6667,14.188C14.1662,14.188 14.5452,13.9459 14.8462,13.6742C15.1228,13.4244 15.4189,13.069 15.7473,12.6748L17.5762,10.4801Z"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M8.2670,1.6185C8.4778,1.2619 8.9377,1.1437 9.2943,1.3545L10.126,1.8462L19.3731,7.1534C19.7324,7.3596 19.8564,7.8179 19.6503,8.1772C19.4441,8.5364 18.9857,8.6605 18.6264,8.4543L17.7828,7.9701L16.278,10.5675L13.7181,9.0947C13.3591,8.8881 12.9006,9.0117 12.694,9.3707C12.4875,9.7298 12.611,10.1883 12.9701,10.3948L15.526,11.8654L14.5646,13.525L10.3598,11.1057C10.0008,10.8991 9.5423,11.0227 9.3357,11.3818C9.1291,11.7408 9.2527,12.1993 9.6118,12.4059L13.8126,14.8229L12.927,16.3515L10.3125,14.8472C9.9535,14.6407 9.495,14.7643 9.2884,15.1233C9.0818,15.4823 9.2054,15.9408 9.5645,16.1474L12.1751,17.6494L11.0558,19.5814C9.7158,21.8943 6.748,22.6868 4.4271,21.3514C2.1062,20.0161 1.3109,17.0585 2.6509,14.7456L9.3727,3.1433L9.3668,3.1399L8.5309,2.6457C8.1744,2.435 8.0562,1.9751 8.2670,1.6185Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M20,17C21.1046,17 22,16.0672 22,14.9166C22,14.1967 21.217,13.2358 20.6309,12.6174C20.2839,12.2512 19.7161,12.2512 19.3691,12.6174C18.783,13.2358 18,14.1967 18,14.9166C18,16.0672 18.8954,17 20,17Z"/>
</vector>

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M5.4669,4.3921C5.7595,4.6853 5.759,5.1601 5.4658,5.4527C3.7872,7.128 2.75,9.4422 2.75,12C2.75,14.5878 3.8116,16.9262 5.525,18.6059C5.8208,18.8959 5.8255,19.3707 5.5356,19.6665C5.2456,19.9623 4.7708,19.967 4.475,19.677C2.4856,17.7269 1.25,15.0071 1.25,12C1.25,9.0278 2.4572,6.3362 4.4062,4.391C4.6994,4.0984 5.1743,4.0989 5.4669,4.3921Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.6164,4.4645C18.9122,4.1745 19.387,4.1792 19.677,4.475C21.5771,6.4133 22.75,9.0704 22.75,12C22.75,14.9645 21.5491,17.6499 19.609,19.5938C19.3164,19.887 18.8415,19.8875 18.5484,19.5949C18.2552,19.3023 18.2547,18.8274 18.5473,18.5342C20.2182,16.86 21.25,14.5512 21.25,12C21.25,9.4787 20.2422,7.1943 18.6059,5.5251C18.3159,5.2293 18.3206,4.7544 18.6164,4.4645Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M8.3092,7.4876C8.5923,7.79 8.5765,8.2647 8.2741,8.5477C7.3252,9.4357 6.75,10.6503 6.75,11.9823C6.75,13.3297 7.3387,14.5573 8.3076,15.4479C8.6125,15.7282 8.6325,16.2027 8.3522,16.5076C8.0719,16.8126 7.5974,16.8325 7.2924,16.5522C6.0397,15.4007 5.25,13.7824 5.25,11.9823C5.25,10.203 6.0215,8.6013 7.2492,7.4525C7.5516,7.1694 8.0262,7.1852 8.3092,7.4876Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M15.7429,7.5256C16.0292,7.2263 16.5039,7.2157 16.8033,7.5021C18.0005,8.6472 18.75,10.2286 18.75,11.9823C18.75,13.7568 17.9825,15.3549 16.7604,16.5031C16.4586,16.7867 15.9839,16.7719 15.7003,16.47C15.4167,16.1682 15.4315,15.6935 15.7333,15.4099C16.6778,14.5225 17.25,13.3108 17.25,11.9823C17.25,10.6692 16.6911,9.4705 15.7664,8.586C15.4671,8.2997 15.4566,7.825 15.7429,7.5256Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M12,14C13.1046,14 14,13.1046 14,12C14,10.8954 13.1046,10 12,10C10.8954,10 10,10.8954 10,12C10,13.1046 10.8954,14 12,14Z"/>
</vector>

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,10C14.2091,10 16,8.2091 16,6C16,3.7909 14.2091,2 12,2C9.7909,2 8,3.7909 8,6C8,8.2091 9.7909,10 12,10Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M16.5,22C14.8501,22 14.0251,22 13.5126,21.4874C13,20.9749 13,20.1499 13,18.5C13,16.8501 13,16.0251 13.5126,15.5126C14.0251,15 14.8501,15 16.5,15C18.1499,15 18.9749,15 19.4874,15.5126C20,16.0251 20,16.8501 20,18.5C20,20.1499 20,20.9749 19.4874,21.4874C18.9749,22 18.1499,22 16.5,22ZM15.3569,16.532C15.1291,16.3042 14.7598,16.3042 14.532,16.532C14.3042,16.7598 14.3042,17.1291 14.532,17.3569L15.675,18.5L14.532,19.6431C14.3042,19.8709 14.3042,20.2402 14.532,20.468C14.7598,20.6958 15.1291,20.6958 15.3569,20.468L16.5,19.325L17.6431,20.468C17.8709,20.6958 18.2402,20.6958 18.468,20.468C18.6958,20.2402 18.6958,19.8709 18.468,19.6431L17.325,18.5L18.468,17.3569C18.6958,17.1291 18.6958,16.7598 18.468,16.532C18.2402,16.3042 17.8709,16.3042 17.6431,16.532L16.5,17.675L15.3569,16.532Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M15.6782,13.5028C15.2051,13.5085 14.7642,13.5258 14.3799,13.5774C13.737,13.6639 13.0334,13.8705 12.4519,14.4519C11.8705,15.0333 11.6639,15.737 11.5775,16.3799C11.4998,16.9576 11.4999,17.6635 11.5,18.414V18.586C11.4999,19.3365 11.4998,20.0424 11.5775,20.6201C11.6381,21.0712 11.7579,21.5522 12.0249,22C12.0166,22 12.0083,22 12,22C4,22 4,19.9853 4,17.5C4,15.0147 7.5817,13 12,13C13.3262,13 14.577,13.1815 15.6782,13.5028Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M2,11.5V12.5C2,15.7875 2,17.4312 2.908,18.5376C3.0742,18.7401 3.2599,18.9258 3.4624,19.092C4.5688,20 6.2125,20 9.5,20C12.7875,20 14.4312,20 15.5376,19.092C15.7401,18.9258 15.9258,18.7401 16.092,18.5376C16.7936,17.6827 16.9531,16.507 16.9893,14.5L17.6584,14.8292C19.6042,15.8021 20.5772,16.2886 21.2886,15.8489C22,15.4093 22,14.3215 22,12.1459V11.8541C22,9.6785 22,8.5908 21.2886,8.1511C20.5772,7.7114 19.6042,8.1979 17.6584,9.1708L16.9893,9.5C16.9531,7.493 16.7936,6.3173 16.092,5.4624C15.9258,5.2599 15.7401,5.0742 15.5376,4.908C14.4312,4 12.7875,4 9.5,4C6.2125,4 4.5688,4 3.4624,4.908C3.2599,5.0742 3.0742,5.2599 2.908,5.4624C2,6.5688 2,8.2125 2,11.5Z"/>
</vector>

View file

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M36.0133814,62 C39.5639046,62 42.9092469,61.3175502 46.0494081,59.9526505 C49.1895694,58.5877509 51.9550523,56.7098988 54.3458569,54.3190942 C56.7366615,51.9282896 58.6100532,49.1672671 59.9660319,46.0360268 C61.3220106,42.9047864 62,39.5549837 62,35.9866186 C62,32.4360954 61.3175502,29.0907531 59.9526505,25.9505919 C58.5877509,22.8104306 56.7098988,20.0404872 54.3190942,17.6407617 C51.9282896,15.2410362 49.1628067,13.3676445 46.0226454,12.0205867 C42.8824841,10.6735289 39.5371419,10 35.9866186,10 C32.4360954,10 29.0952136,10.6735289 25.9639732,12.0205867 C22.8327329,13.3676445 20.06725,15.2410362 17.6675244,17.6407617 C15.2677989,20.0404872 13.3899468,22.8104306 12.0339681,25.9505919 C10.6779894,29.0907531 10,32.4360954 10,35.9866186 C10,39.5549837 10.6824498,42.9047864 12.0473495,46.0360268 C13.4122491,49.1672671 15.2901012,51.9282896 17.6809058,54.3190942 C20.0717104,56.7098988 22.8327329,58.5877509 25.9639732,59.9526505 C29.0952136,61.3175502 32.4450163,62 36.0133814,62 Z M35.9866186,57.9320638 C34.0775433,57.9320638 32.1773889,57.6733574 30.2861554,57.1559444 C28.3949219,56.6385315 26.606279,55.8980957 24.9202265,54.9346372 C23.234174,53.9711786 21.7399211,52.802539 20.4374678,51.4287185 C21.3830846,49.9835306 22.6364728,48.7479842 24.1976325,47.7220793 C25.7587922,46.6961743 27.5429748,45.911134 29.5501801,45.3669583 C31.5573855,44.8227826 33.702865,44.5506948 35.9866186,44.5506948 C38.2525305,44.5506948 40.3846286,44.8183222 42.382913,45.3535769 C44.3811975,45.8888317 46.16538,46.6694116 47.7354606,47.6953165 C49.3055413,48.7212215 50.5633899,49.9656888 51.5090067,51.4287185 C50.2243953,52.802539 48.7390633,53.9711786 47.0530108,54.9346372 C45.3669583,55.8980957 43.5783153,56.6385315 41.6870818,57.1559444 C39.7958483,57.6733574 37.8956939,57.9320638 35.9866186,57.9320638 Z M35.9866186,40.241894 C34.3451707,40.2240522 32.8642992,39.7913879 31.5440041,38.9439012 C30.223709,38.0964145 29.1755018,36.9545377 28.3993824,35.5182707 C27.623263,34.0820038 27.2352033,32.4450163 27.2352033,30.6073083 C27.2352033,28.8944931 27.623263,27.3244124 28.3993824,25.8970664 C29.1755018,24.4697204 30.2281695,23.3278435 31.5573855,22.4714359 C32.8866015,21.6150283 34.3630125,21.1868245 35.9866186,21.1868245 C37.6102247,21.1868245 39.0821753,21.6150283 40.4024704,22.4714359 C41.7227655,23.3278435 42.7754332,24.4697204 43.5604735,25.8970664 C44.3455138,27.3244124 44.738034,28.8944931 44.738034,30.6073083 C44.738034,32.4450163 44.3499743,34.0909247 43.5738549,35.5450335 C42.7977355,36.9991422 41.7495282,38.14994 40.4292331,38.9974267 C39.1089381,39.8449134 37.6280666,40.2597358 35.9866186,40.241894 Z" android:pathData="M12,6m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/>
/> <path
android:fillColor="#FF000000"
android:pathData="M12,17c-3.866,0 -7,1.343 -7,3s3.134,4 7,4s7,-1.343 7,-3s-3.134,-4 -7,-4z"/>
</vector> </vector>

View file

@ -1,11 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M37,10.5 C51.9116882,10.5 64,21.5336458 64,35.1443377 C64,48.7550296 51.9116882,59.7886755 37,59.7886755 C32.9373498,59.7886755 29.0842719,58.9696748 25.6281322,57.5026921 C24.5962632,58.3181165 23.6686095,58.9358881 22.8452502,59.3570726 L22.3020946,59.6280004 C20.0852871,60.7030681 18.3853474,61.1218474 14.8625755,61.4912843 C13.8315923,61.5994047 13.1452722,60.6809798 13.948451,59.8798765 C15.7272821,58.105642 16.6871721,54.9282281 17.1758539,51.8746196 C12.7215027,47.480265 10,41.6023931 10,35.1443377 C10,21.5336458 22.0883118,10.5 37,10.5 Z M43.5421053,39.1315789 L25.9578947,39.1315789 C24.3519088,39.1315789 23.05,40.4334878 23.05,42.0394737 C23.05,43.6454596 24.3519088,44.9473684 25.9578947,44.9473684 L43.5421053,44.9473684 C45.1480912,44.9473684 46.45,43.6454596 46.45,42.0394737 C46.45,40.4334878 45.1480912,39.1315789 43.5421053,39.1315789 Z M48.0421053,28.3947368 L25.9578947,28.3947368 C24.3519088,28.3947368 23.05,29.6966457 23.05,31.3026316 C23.05,32.9086175 24.3519088,34.2105263 25.9578947,34.2105263 L48.0421053,34.2105263 C49.6480912,34.2105263 50.95,32.9086175 50.95,31.3026316 C50.95,29.6966457 49.6480912,28.3947368 48.0421053,28.3947368 Z" android:pathData="M12,22C17.5228,22 22,17.5228 22,12C22,6.4772 17.5228,2 12,2C6.4772,2 2,6.4772 2,12C2,13.5997 2.3756,15.1116 3.0435,16.4525C3.2209,16.8088 3.28,17.2161 3.1771,17.6006L2.5815,19.8267C2.323,20.793 3.207,21.677 4.1734,21.4185L6.3994,20.8229C6.7839,20.72 7.1912,20.7791 7.5475,20.9565C8.8884,21.6244 10.4003,22 12,22Z"/>
/> <path
android:fillColor="#FFFFFFFF"
android:pathData="M15,12C15,12.5523 15.4477,13 16,13C16.5523,13 17,12.5523 17,12C17,11.4477 16.5523,11 16,11C15.4477,11 15,11.4477 15,12Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11,12C11,12.5523 11.4477,13 12,13C12.5523,13 13,12.5523 13,12C13,11.4477 12.5523,11 12,11C11.4477,11 11,11.4477 11,12Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M7,12C7,12.5523 7.4477,13 8,13C8.5523,13 9,12.5523 9,12C9,11.4477 8.5523,11 8,11C7.4477,11 7,11.4477 7,12Z"/>
</vector> </vector>

View file

@ -1,10 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M33.5957822,12.6581573 L33.5957822,59.3418427 C33.5957822,61.0355818 32.0489476,62.3058862 30.4234603,61.9353808 C18.7042219,59.2889133 10,48.676579 10,36 C10,23.323421 18.7042219,12.7110867 30.4234603,10.0646192 C32.0489476,9.69411379 33.5957822,10.9644182 33.5957822,12.6581573 Z M38.9179419,12.6581573 L38.9179419,30.6806005 C38.9179419,32.1361576 40.097731,33.3270679 41.5396955,33.3270679 L59.3414023,33.3270679 C61.0193246,33.3270679 62.2777663,31.7656521 61.9107208,30.0983776 C59.6822302,20.1476601 51.9480572,12.3141165 42.1164813,10.0646192 C40.4647765,9.69411379 38.9179419,10.9644182 38.9179419,12.6581573 Z M38.9179419,41.3193995 L38.9179419,59.3418427 C38.9179419,61.0355818 40.4647765,62.3058862 42.1164813,61.9353808 C51.9742747,59.6858835 59.7084478,51.8258752 61.9369383,41.8751577 C62.3039838,40.2343479 61.0193246,38.6464674 59.3676198,38.6464674 L41.565913,38.6464674 C40.097731,38.6729321 38.9179419,39.8638424 38.9179419,41.3193995 Z" /> android:fillType="evenOdd"
android:pathData="M8.9393,14.4393C8.5,14.8787 8.5,15.5858 8.5,17V19C8.5,20.4142 8.5,21.1213 8.9393,21.5607C9.3787,22 10.0858,22 11.5,22H12.5C13.9142,22 14.6213,22 15.0607,21.5607C15.5,21.1213 15.5,20.4142 15.5,19V17C15.5,15.5858 15.5,14.8787 15.0607,14.4393C14.6213,14 13.9142,14 12.5,14H11.5C10.0858,14 9.3787,14 8.9393,14.4393ZM10.25,18C10.25,17.5858 10.5858,17.25 11,17.25H13C13.4142,17.25 13.75,17.5858 13.75,18C13.75,18.4142 13.4142,18.75 13,18.75H11C10.5858,18.75 10.25,18.4142 10.25,18Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M7,18L7,16.9184C6.9999,16.2823 6.9998,15.6641 7.0683,15.1542C7.1456,14.5794 7.3339,13.9235 7.8787,13.3787C8.4235,12.8339 9.0794,12.6456 9.6542,12.5683C10.1641,12.4998 10.7823,12.4999 11.4184,12.5H12.5816C13.2177,12.4999 13.8359,12.4998 14.3458,12.5683C14.9206,12.6456 15.5765,12.8339 16.1213,13.3787C16.6662,13.9235 16.8544,14.5794 16.9317,15.1542C17.0003,15.6641 17.0002,16.2823 17,16.9184L17,17.9563C19.8188,17.6089 22,15.2327 22,12.3529C22,9.8811 20.393,7.7802 18.1551,7.015C17.8371,4.1937 15.4159,2 12.4762,2C9.3203,2 6.7619,4.5283 6.7619,7.6471C6.7619,8.3369 6.8871,8.9978 7.1162,9.6089C6.8475,9.5567 6.5698,9.5294 6.2857,9.5294C3.9188,9.5294 2,11.4256 2,13.7647C2,16.1038 3.9188,18 6.2857,18H7Z"/>
</vector> </vector>

View file

@ -1,10 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M20.7241379,16 L50.7241379,16 C53.4855617,16 55.7241379,18.2385763 55.7241379,21 L55.7241379,43 C55.7241379,44.6568542 54.3809922,46 52.7241379,46 L18.7241379,46 C17.0672837,46 15.7241379,44.6568542 15.7241379,43 L15.7241379,21 C15.7241379,18.2385763 17.9627142,16 20.7241379,16 Z M11.1418324,50 L60.8581676,50 C62.1355532,50 63.1710789,51.0355257 63.1710789,52.3129114 C63.1710789,52.5458925 63.1358776,52.7775364 63.0666667,53 L62.7667814,53.9639169 C62.3900232,55.1749255 61.2692514,56 60.0009894,56 L11.9990106,56 C10.7307486,56 9.60997682,55.1749255 9.23321859,53.9639169 L8.93333333,53 C8.55386477,51.7802796 9.23502345,50.4838808 10.4547438,50.1044123 C10.6772074,50.0352014 10.9088513,50 11.1418324,50 Z" /> android:pathData="M2,14.5C2,13.0955 2,12.3933 2.3371,11.8889C2.483,11.6705 2.6705,11.483 2.8889,11.3371C3.3933,11 4.0955,11 5.5,11C6.9045,11 7.6067,11 8.1111,11.3371C8.3295,11.483 8.517,11.6705 8.6629,11.8889C9,12.3933 9,13.0955 9,14.5V18.5C9,19.9045 9,20.6067 8.6629,21.1111C8.517,21.3295 8.3295,21.517 8.1111,21.6629C7.6067,22 6.9045,22 5.5,22C4.0955,22 3.3933,22 2.8889,21.6629C2.6705,21.517 2.483,21.3295 2.3371,21.1111C2,20.6067 2,19.9045 2,18.5V14.5Z"/>
<path
android:fillColor="#FF000000"
android:fillType="evenOdd"
android:pathData="M22,10V14C22,17.7712 22,19.6569 20.8284,20.8284C19.6569,22 17.7712,22 14,22C12.3009,22 10.9846,22 9.9439,21.8929C10.2787,21.368 10.3977,20.8107 10.4502,20.2949C10.5001,19.8039 10.5001,19.2114 10.5,18.566L10.5,18.5V14.5L10.5,14.434C10.5001,13.7886 10.5001,13.1961 10.4502,12.7051C10.396,12.1723 10.2708,11.5953 9.9101,11.0555C9.6548,10.6733 9.3267,10.3452 8.9445,10.0899C8.4047,9.7292 7.8277,9.604 7.2949,9.5498C6.9162,9.5113 6.4772,9.5025 6.0001,9.5006C6.0024,6.0599 6.0529,4.2902 7.1716,3.1716C8.3431,2 10.2288,2 14,2C17.7712,2 19.6569,2 20.8284,3.1716C22,4.3431 22,6.2288 22,10ZM11.25,19C11.25,18.5858 11.5858,18.25 12,18.25H17C17.4142,18.25 17.75,18.5858 17.75,19C17.75,19.4142 17.4142,19.75 17,19.75H12C11.5858,19.75 11.25,19.4142 11.25,19Z"/>
</vector> </vector>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M31.6529498,15 L18.094368,15 C15.2924656,15 13.0254718,17.30625 13.0254718,20.125 L13,47.3103859 C12.9969599,51.47557 16.3060979,54.8697036 20.4398986,55.0016331 L20.689612,55.0056143 L52.310388,55 C56.5572434,55 60,51.5572434 60,47.310388 L60,28.4105422 C60,24.1636868 56.5572434,20.7209302 52.310388,20.7209302 L40.6274923,20.7209302 C39.2562019,20.7209302 37.9502179,20.1351534 37.0382728,19.1110472 L34.5243254,16.2879065 C33.7947694,15.4686214 32.7499822,15 31.6529498,15 Z" /> android:fillType="evenOdd"
android:pathData="M2.0694,5.2584C2,5.626 2,6.0672 2,6.9498V14C2,17.7712 2,19.6569 3.1716,20.8284C4.3431,22 6.2288,22 10,22H14C17.7712,22 19.6569,22 20.8284,20.8284C22,19.6569 22,17.7712 22,14V11.7979C22,9.1655 22,7.8494 21.2305,6.9938C21.1598,6.9151 21.0849,6.8402 21.0062,6.7695C20.1506,6 18.8345,6 16.2021,6H15.8284C14.6747,6 14.0979,6 13.5604,5.8468C13.2651,5.7626 12.9804,5.6447 12.7121,5.4954C12.2237,5.2237 11.8158,4.8158 11,4L10.4497,3.4498C10.1763,3.1763 10.0396,3.0396 9.8959,2.9205C9.2765,2.407 8.5167,2.0923 7.7156,2.0174C7.5298,2 7.3364,2 6.9498,2C6.0672,2 5.626,2 5.2584,2.0694C3.6403,2.3746 2.3746,3.6403 2.0694,5.2584ZM12.25,10C12.25,9.5858 12.5858,9.25 13,9.25H18C18.4142,9.25 18.75,9.5858 18.75,10C18.75,10.4142 18.4142,10.75 18,10.75H13C12.5858,10.75 12.25,10.4142 12.25,10Z"/>
</vector> </vector>

View file

@ -1,10 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M43.6561431,47.7009378 C43.0650889,50.9529347 42.2301421,53.9044554 41.1923882,56.3950649 C40.064777,59.1013318 38.8452574,60.9352526 37.770116,61.9408776 C37.1856706,61.980002 36.5951707,62 36,62 C35.4048293,62 34.8143294,61.980002 34.2291356,61.9406414 C33.1547426,60.9352526 31.935223,59.1013318 30.8076118,56.3950649 C29.7698579,53.9044554 28.9349111,50.9529347 28.3438569,47.7009378 L43.6561431,47.7009378 Z M23.9452322,47.701583 C24.4271124,50.5582 25.0801587,53.2045572 25.8738471,55.5673963 L26.1293322,56.3046257 C26.7187882,57.9536277 27.3787447,59.4532243 28.097986,60.7764975 C21.4082675,58.6455 15.8947679,53.880404 12.7752543,47.7005877 L23.9452322,47.701583 Z M59.2247457,47.7005877 C56.1052321,53.880404 50.5917325,58.6455 43.9029941,60.7771284 C44.6212553,59.4532243 45.2812118,57.9536277 45.8706678,56.3046257 L46.1261529,55.5673963 C46.9198413,53.2045572 47.5728876,50.5582 48.0547678,47.701583 L59.2247457,47.7005877 Z M23.3646208,28.6318438 C23.1262931,30.9942887 23,33.4617476 23,36 C23,38.5382524 23.1262931,41.0057113 23.3646208,43.3681562 L11.0584398,43.3669617 C10.3696415,41.0312987 10,38.5588165 10,36 C10,33.4411835 10.3696415,30.9687013 11.0584398,28.6330383 L23.3646208,28.6318438 Z M44.2784268,28.6329613 C44.5329528,30.9983796 44.6666667,33.4708525 44.6666667,36 C44.6666667,38.5291475 44.5329528,41.0016204 44.2784268,43.3670387 L27.7215732,43.3670387 C27.4670472,41.0016204 27.3333333,38.5291475 27.3333333,36 C27.3333333,33.4708525 27.4670472,30.9983796 27.7215732,28.6329613 L44.2784268,28.6329613 Z M60.9415602,28.6330383 C61.6303585,30.9687013 62,33.4411835 62,36 C62,38.5588165 61.6303585,41.0312987 60.9415602,43.3669617 L48.6353792,43.3681562 C48.8737069,41.0057113 49,38.5382524 49,36 C49,33.4617476 48.8737069,30.9942887 48.6353792,28.6318438 L60.9415602,28.6330383 Z M28.097986,11.2235025 C27.3787447,12.5467757 26.7187882,14.0463723 26.1293322,15.6953743 L25.8738471,16.4326037 C25.080066,18.7957188 24.4269598,21.4424181 23.9450634,24.2994178 L12.7747482,24.300415 C15.8421931,18.2230928 21.2248217,13.5138214 27.7636127,11.3316224 L28.097986,11.2235025 Z M36,10 C36.5951941,10 37.1857171,10.0199995 37.7709336,10.0593632 C38.8452574,11.0647474 40.064777,12.8986682 41.1923882,15.6049351 C42.2302487,18.0958005 43.0652604,21.0476716 43.6563252,24.3000642 L28.3436748,24.3000642 C28.9347396,21.0476716 29.7697513,18.0958005 30.8076118,15.6049351 C31.935223,12.8986682 33.1547426,11.0647474 34.229884,10.0591224 C34.8142829,10.0199995 35.4048059,10 36,10 Z M59.2252518,24.300415 L48.0549366,24.2994178 C47.5730402,21.4424181 46.919934,18.7957188 46.1261529,16.4326037 L45.8706678,15.6953743 C45.2812118,14.0463723 44.6212553,12.5467757 43.902014,11.2235025 C50.5918084,13.3543682 56.1057476,18.1199512 59.2252518,24.300415 Z" /> android:fillType="evenOdd"
android:pathData="M2.0278,11.25C2.4114,6.0775 6.7296,2 12.0001,2C11.1693,2 10.4295,2.3642 9.8209,2.9211C9.2154,3.4753 8.7037,4.2488 8.2898,5.1632C7.8735,6.0829 7.5501,7.1587 7.3313,8.3261C7.1558,9.2619 7.049,10.2485 7.0134,11.25H2.0278ZM2.0278,12.75H7.0134C7.049,13.7515 7.1558,14.7381 7.3313,15.6739C7.5501,16.8413 7.8735,17.9171 8.2898,18.8368C8.7037,19.7512 9.2154,20.5247 9.8209,21.0789C10.4295,21.6358 11.1693,22 12.0001,22C6.7296,22 2.4114,17.9226 2.0278,12.75Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M12.0001,3.3954C11.7251,3.3954 11.3699,3.5124 10.9567,3.8904C10.5406,4.2713 10.1239,4.8682 9.7559,5.6814C9.3902,6.4892 9.0933,7.4644 8.889,8.5542C8.7281,9.4124 8.6282,10.3222 8.5932,11.25H15.4071C15.372,10.3222 15.2722,9.4124 15.1113,8.5542C14.907,7.4644 14.6101,6.4892 14.2444,5.6814C13.8763,4.8682 13.4597,4.2713 13.0435,3.8904C12.6304,3.5124 12.2751,3.3954 12.0001,3.3954Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M8.889,15.4458C9.0933,16.5356 9.3902,17.5108 9.7559,18.3186C10.1239,19.1319 10.5406,19.7287 10.9567,20.1096C11.3698,20.4876 11.7251,20.6047 12.0001,20.6047C12.2751,20.6047 12.6304,20.4876 13.0435,20.1096C13.4597,19.7287 13.8763,19.1319 14.2444,18.3186C14.6101,17.5108 14.907,16.5356 15.1113,15.4458C15.2722,14.5876 15.372,13.6778 15.4071,12.75H8.5932C8.6282,13.6778 8.7281,14.5876 8.889,15.4458Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M12.0001,2C12.831,2 13.5708,2.3642 14.1793,2.9211C14.7849,3.4753 15.2966,4.2488 15.7104,5.1632C16.1267,6.0829 16.4501,7.1587 16.669,8.3261C16.8445,9.2619 16.9512,10.2485 16.9868,11.25H21.9724C21.5889,6.0775 17.2707,2 12.0001,2Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M16.669,15.6739C16.4501,16.8413 16.1267,17.9171 15.7104,18.8368C15.2966,19.7512 14.7849,20.5247 14.1793,21.0789C13.5708,21.6358 12.831,22 12.0001,22C17.2707,22 21.5889,17.9226 21.9724,12.75H16.9868C16.9512,13.7515 16.8445,14.7381 16.669,15.6739Z"/>
</vector> </vector>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M47.8260869,17.9 C52.8518782,17.9 56.926087,21.9742088 56.926087,27 L56.926087,45 C56.926087,50.0257912 52.8518782,54.1 47.826087,54.1 L18,54.1 C12.9742088,54.1 8.9,50.0257912 8.9,45 L8.9,27 C8.9,21.9742088 12.9742088,17.9 18,17.9 L47.8260869,17.9 Z M47.826087,22.1 L18,22.1 L17.4990035,22.1252982 C15.0281487,22.3762271 13.1,24.4629419 13.1,27 L13.1,45 C13.1,47.7061953 15.2938047,49.9 18,49.9 L47.826087,49.9 C50.5322822,49.9 52.726087,47.7061953 52.726087,45 L52.726087,27 C52.726087,24.2938047 50.5322822,22.1 47.826087,22.1 Z M21.5981366,25.1025641 C23.3640757,25.1025641 24.7956522,26.5341406 24.7956522,28.3000796 L24.7956522,43.6999204 C24.7956522,45.4658594 23.3640757,46.8974359 21.5981366,46.8974359 L19.2975155,46.8974359 C17.5315765,46.8974359 16.1,45.4658594 16.1,43.6999204 L16.1,28.3000796 C16.1,26.5341406 17.5315765,25.1025641 19.2975155,25.1025641 L21.5981366,25.1025641 Z M60.0434783,28.7179487 C63.8854591,28.7179487 67,32.2307915 67,36.5641026 C67,40.8974137 63.8854591,44.4102564 60.0434783,44.4102564 L60.0434783,28.7179487 Z" /> android:fillType="evenOdd"
android:pathData="M2,12C2,8.2288 2,6.3431 3.1716,5.1716C4.3431,4 6.2288,4 10,4H11.5C15.2712,4 17.1569,4 18.3284,5.1716C19.2715,6.1147 19.4554,7.5204 19.4913,10H20C20.9428,10 21.4142,10 21.707,10.2929C22,10.5858 22,11.0572 22,12C22,12.9428 22,13.4142 21.707,13.7071C21.4142,14 20.9428,14 20,14H19.4913C19.4554,16.4796 19.2715,17.8853 18.3284,18.8284C17.1569,20 15.2712,20 11.5,20H10C6.2288,20 4.3431,20 3.1716,18.8284C2,17.6569 2,15.7712 2,12ZM6.6358,8.3444C6.9979,8.1432 7.4545,8.2737 7.6556,8.6358L7,9C7.6556,8.6358 7.6562,8.6368 7.6563,8.6371L7.6571,8.6385L7.6588,8.6415L7.6626,8.6486L7.6723,8.667C7.6795,8.6811 7.6884,8.6988 7.6986,8.7203C7.7191,8.7634 7.7451,8.8214 7.7745,8.8949C7.8334,9.0421 7.9059,9.251 7.9765,9.5262C8.1178,10.0772 8.25,10.8899 8.25,12C8.25,13.1101 8.1178,13.9228 7.9765,14.4738C7.9059,14.749 7.8334,14.9579 7.7745,15.1051C7.7451,15.1787 7.7191,15.2367 7.6986,15.2797C7.6884,15.3012 7.6795,15.3189 7.6723,15.333L7.6626,15.3514L7.6588,15.3585L7.6571,15.3615L7.6563,15.3629C7.4545,15.7263 6.9979,15.8568 6.6358,15.6556C6.2768,15.4562 6.1455,15.0056 6.3393,14.645L6.3443,14.6348C6.3512,14.6204 6.3643,14.5917 6.3818,14.548C6.4167,14.4608 6.4691,14.3135 6.5235,14.1012C6.6322,13.6772 6.75,12.9899 6.75,12C6.75,11.0101 6.6322,10.3228 6.5235,9.8988C6.4691,9.6865 6.4167,9.5392 6.3818,9.452C6.3643,9.4083 6.3512,9.3796 6.3443,9.3652L6.3393,9.355C6.1455,8.9944 6.2768,8.5438 6.6358,8.3444ZM11.1556,8.6358C10.9545,8.2737 10.4979,8.1432 10.1358,8.3444C9.7768,8.5438 9.6455,8.9944 9.8393,9.355L9.8443,9.3652C9.8512,9.3796 9.8643,9.4083 9.8818,9.452C9.9167,9.5392 9.9691,9.6865 10.0235,9.8988C10.1322,10.3228 10.25,11.0101 10.25,12C10.25,12.9899 10.1322,13.6772 10.0235,14.1012C9.9691,14.3135 9.9167,14.4608 9.8818,14.548C9.8643,14.5917 9.8512,14.6204 9.8443,14.6348L9.8393,14.645C9.6455,15.0056 9.7768,15.4562 10.1358,15.6556C10.4979,15.8568 10.9545,15.7263 11.1556,15.3642C11.4545,14.8065 11.75,13.6101 11.75,12C11.75,10.3899 11.4545,9.1935 11.1556,8.6358ZM13.6358,8.3444C13.9979,8.1432 14.4545,8.2737 14.6556,8.6358L14,9C14.6556,8.6358 14.6562,8.6368 14.6563,8.6371L14.6571,8.6385L14.6588,8.6415L14.6626,8.6486L14.6723,8.667C14.6795,8.6811 14.6884,8.6988 14.6986,8.7203C14.7191,8.7634 14.7451,8.8214 14.7745,8.8949C14.8334,9.0421 14.9059,9.251 14.9765,9.5262C15.1178,10.0772 15.25,10.8899 15.25,12C15.25,13.1101 15.1178,13.9228 14.9765,14.4738C14.9059,14.749 14.8334,14.9579 14.7745,15.1051C14.7451,15.1787 14.7191,15.2367 14.6986,15.2797C14.6884,15.3012 14.6795,15.3189 14.6723,15.333L14.6626,15.3514L14.6588,15.3585L14.6571,15.3615L14.6563,15.3629C14.4545,15.7263 13.9979,15.8568 13.6358,15.6556C13.2768,15.4562 13.1455,15.0056 13.3393,14.645L13.3443,14.6348C13.3512,14.6204 13.3643,14.5917 13.3818,14.548C13.4167,14.4608 13.4691,14.3135 13.5235,14.1012C13.6322,13.6772 13.75,12.9899 13.75,12C13.75,11.0101 13.6322,10.3228 13.5235,9.8988C13.4691,9.6865 13.4167,9.5392 13.3818,9.452C13.3643,9.4083 13.3512,9.3796 13.3443,9.3652L13.3393,9.355C13.1455,8.9944 13.2768,8.5438 13.6358,8.3444Z"/>
</vector> </vector>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M45.0344828,12 C54.4042792,12 62,19.6071258 62,28.990991 C62,38.3748562 54.4042792,45.981982 45.0344828,45.981982 C42.7466901,45.981982 40.5646633,45.528465 38.5731454,44.7063014 C38.3355524,44.6082152 37.4936309,44.4692916 37.3254604,45.0025465 L37.07207,45.7703609 C36.7432209,46.7370274 36.2269607,48.1762204 35.5232893,50.0879398 C35.4573946,50.2969196 35.1851865,50.462725 34.706665,50.585356 L28.1929984,50.5405405 C27.9022849,50.5405405 27.6358094,50.6929524 27.4868919,50.937039 L27.4367406,51.0327501 L24.5632594,57.5077904 C24.4451895,57.7738467 24.1978966,57.9558204 23.9147007,57.9929743 L23.8070016,58 L15.6551724,58 C14.7825971,58 14.0677254,57.323777 14.0045399,56.4660553 L14,56.3423423 L14,49.8944911 C14,49.0491564 14.3222943,48.2383584 14.8966794,47.6255559 L15.0336729,47.487729 L28.9182017,34.3140603 C28.3671371,32.6396811 28.0689655,30.8502092 28.0689655,28.990991 C28.0689655,19.6071258 35.6646863,12 45.0344828,12 Z M47.7777778,21.7307692 C45.3231789,21.7307692 43.3333333,23.7110482 43.3333333,26.1538462 C43.3333333,28.5966441 45.3231789,30.5769231 47.7777778,30.5769231 C50.2323767,30.5769231 52.2222222,28.5966441 52.2222222,26.1538462 C52.2222222,23.7110482 50.2323767,21.7307692 47.7777778,21.7307692 Z" /> android:fillType="evenOdd"
android:pathData="M3.3775,5.0824C3,5.6203 3,7.2191 3,10.4167V11.9914C3,17.6294 7.239,20.3655 9.8986,21.5273C10.62,21.8424 10.9807,22 12,22C13.0193,22 13.38,21.8424 14.1014,21.5273C16.761,20.3655 21,17.6294 21,11.9914V10.4167C21,7.2191 21,5.6203 20.6225,5.0824C20.245,4.5445 18.7417,4.03 15.7351,3.0008L15.1623,2.8047C13.595,2.2682 12.8114,2 12,2C11.1886,2 10.405,2.2682 8.8377,2.8047L8.2649,3.0008C5.2583,4.03 3.755,4.5445 3.3775,5.0824ZM15.0595,10.4995C15.3353,10.1905 15.3085,9.7164 14.9995,9.4406C14.6905,9.1647 14.2164,9.1915 13.9405,9.5005L10.9286,12.8739L10.0595,11.9005C9.7836,11.5915 9.3095,11.5647 9.0005,11.8406C8.6915,12.1164 8.6647,12.5905 8.9406,12.8995L10.3691,14.4995C10.5114,14.6589 10.7149,14.75 10.9286,14.75C11.1422,14.75 11.3457,14.6589 11.488,14.4995L15.0595,10.4995Z"/>
</vector> </vector>

View file

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="72" android:viewportWidth="24"
android:viewportHeight="72"> android:viewportHeight="24">
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF000000"
android:pathData="M14.1064639,45.4198379 L22.8897338,45.4198379 L32.6471483,55.1825697 C34.5155894,57.052029 37.7186312,55.716701 37.7186312,53.0757188 L37.7186312,19.9299089 C37.7186312,17.2889267 34.5155894,15.9535987 32.6471483,17.823058 L22.8897338,27.6154638 L14.1064639,27.6154638 C11.8385265,27.6154638 10,29.4503157 10,31.7137203 L10,41.3215813 C10,43.5849859 11.8385265,45.4198379 14.1064639,45.4198379 Z M47.2123874,11.2051421 C56.5979444,15.2844501 64,24.9499756 64,35.9999887 C64,47.1079781 56.3772118,56.9140313 46.8682565,60.8147253 C45.6096658,61.3310152 44.1700027,60.7313006 43.6526789,59.4752254 C43.1353551,58.2191502 43.7362707,56.7823644 44.9948614,56.2660746 C52.7661008,53.0782136 59.0722433,44.9659104 59.0722433,35.9999887 C59.0722433,27.0616007 52.9451,19.0608421 45.2448094,15.7140128 C43.9972257,15.171767 43.4263166,13.7228449 43.9696483,12.4777547 C44.5129799,11.2326644 45.9648037,10.6628964 47.2123874,11.2051421 Z M45.076906,22.5813884 C49.7667961,25.1874073 53.0152091,30.3830889 53.0152091,35.9999887 C53.0152091,41.5003452 49.8769427,46.722944 45.3691502,49.386172 C44.1981877,50.0779829 42.6869883,49.691451 41.993792,48.5228288 C41.3005956,47.3542066 41.6879016,45.8460276 42.8588641,45.1542167 C45.9023133,43.3561306 48.0874525,39.7196945 48.0874525,35.9999887 C48.0874525,32.2194927 45.8490315,28.6392451 42.679748,26.8781777 C41.4908462,26.2175438 41.063673,24.7201251 41.7256299,23.5335995 C42.3875868,22.3470739 43.8880043,21.9207545 45.076906,22.5813884 Z" /> android:pathData="M8.3518,20.2418C9.1929,21.311 10.5142,22 12,22C13.4858,22 14.8071,21.311 15.6482,20.2418C13.2264,20.57 10.7736,20.57 8.3518,20.2418Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.7491,9V9.7041C18.7491,10.5491 18.9903,11.3752 19.4422,12.0782L20.5496,13.8012C21.5612,15.3749 20.789,17.5139 19.0296,18.0116C14.4273,19.3134 9.5727,19.3134 4.9704,18.0116C3.211,17.5139 2.4388,15.3749 3.4504,13.8012L4.5578,12.0782C5.0097,11.3752 5.2509,10.5491 5.2509,9.7041V9C5.2509,5.134 8.2726,2 12,2C15.7274,2 18.7491,5.134 18.7491,9Z"/>
</vector> </vector>

Binary file not shown.

Binary file not shown.

View file

@ -263,4 +263,7 @@
<string name="RemindTodayAt">\'Отправить сегодня в\' HH:mm</string> <string name="RemindTodayAt">\'Отправить сегодня в\' HH:mm</string>
<string name="RemindDayAt">\'Отправить\' d MMM \'в\' HH:mm</string> <string name="RemindDayAt">\'Отправить\' d MMM \'в\' HH:mm</string>
<string name="RemindDayYearAt">\'Напомнить\' d MMM yyyy \'в\' HH:mm</string> <string name="RemindDayYearAt">\'Напомнить\' d MMM yyyy \'в\' HH:mm</string>
<string name="FoxRefreshServers">Обновить серверы</string>
<string name="FoxRefreshServersLoading">Загрузка списка серверов…</string>
<string name="FoxRefreshServersDone">Список серверов обновлён</string>
</resources> </resources>

View file

@ -2662,6 +2662,9 @@
<string name="ProxyConnections">Connections</string> <string name="ProxyConnections">Connections</string>
<string name="ProxyAddedSuccess">Proxy added.</string> <string name="ProxyAddedSuccess">Proxy added.</string>
<string name="AddProxy">Add Proxy</string> <string name="AddProxy">Add Proxy</string>
<string name="FoxRefreshServers">Refresh Servers</string>
<string name="FoxRefreshServersLoading">Fetching server list…</string>
<string name="FoxRefreshServersDone">Server list updated</string>
<string name="DeleteProxy">Delete Proxy?</string> <string name="DeleteProxy">Delete Proxy?</string>
<string name="DeleteProxyTitle">Delete Proxy</string> <string name="DeleteProxyTitle">Delete Proxy</string>
<string name="DeleteProxyConfirm">Are you sure you want to delete this proxy?</string> <string name="DeleteProxyConfirm">Are you sure you want to delete this proxy?</string>

View file

@ -165,6 +165,14 @@
<string name="FoxPremiumFeatureIconsAbout">Premium, Space and Cloud app icons</string> <string name="FoxPremiumFeatureIconsAbout">Premium, Space and Cloud app icons</string>
<string name="FoxPremiumButton">Support FoxiGram</string> <string name="FoxPremiumButton">Support FoxiGram</string>
<string name="FoxSponsorBadgeInfo">This badge is granted for supporting the project with 444+ rubles.</string> <string name="FoxSponsorBadgeInfo">This badge is granted for supporting the project with 444+ rubles.</string>
<string name="FoxSpaceTitle">Space FoxiGram</string>
<string name="FoxSpaceSubtitle">Unlock the cosmos and enjoy exclusive space perks</string>
<string name="FoxSpaceFeatureIconsTitle">Space app icon</string>
<string name="FoxSpaceFeatureIconsAbout">Exclusive Space icon for your app</string>
<string name="FoxCloudTitle">Cloud FoxiGram</string>
<string name="FoxCloudSubtitle">Float above the rest with exclusive cloud perks</string>
<string name="FoxCloudFeatureIconsTitle">Cloud app icon</string>
<string name="FoxCloudFeatureIconsAbout">Exclusive Cloud icon for your app</string>
<string name="FoxSupportProject">Support the project</string> <string name="FoxSupportProject">Support the project</string>
<string name="FoxSupportProjectAbout">Subscribe to GhostCloud via our bot</string> <string name="FoxSupportProjectAbout">Subscribe to GhostCloud via our bot</string>
<string name="FoxTopSponsors">Top sponsors</string> <string name="FoxTopSponsors">Top sponsors</string>

View file

@ -26,6 +26,11 @@ subprojects {
coreLibraryDesugaringEnabled true coreLibraryDesugaringEnabled true
} }
tasks.withType(JavaCompile).configureEach {
options.forkOptions.javaHome = file('C:/jdk21/jdk-21.0.7+6')
options.fork = true
}
defaultConfig { defaultConfig {
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 36 targetSdkVersion 36

View file

@ -17,7 +17,7 @@
android.useAndroidX=true android.useAndroidX=true
android.enableAppCompileTimeRClass=true android.enableAppCompileTimeRClass=true
org.gradle.java.home=C:/jdk21/jdk-21.0.7+6 org.gradle.java.home=C:/jdk21/jdk-21.0.7+6
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -Djava.home=C:/jdk21/jdk-21.0.7+6 org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
org.gradle.parallel=true org.gradle.parallel=true
APP_VERSION_CODE=6750 APP_VERSION_CODE=6750