diff --git a/.github/workflows/tandroid.yml b/.github/workflows/tandroid.yml index 0870735d424..b273f8a911f 100644 --- a/.github/workflows/tandroid.yml +++ b/.github/workflows/tandroid.yml @@ -68,6 +68,8 @@ jobs: echo "CHECK_UPDATES=${{ matrix.updates }}" >> $vars echo "USER_ID_OWNER=${{ secrets.USER_ID_OWNER }}" >> $vars echo "UPDATE_CHANNEL_USERNAME=${{ secrets.UPDATE_CHANNEL_USERNAME }}" >> $vars + echo "LASTFM_API_KEY=${{ secrets.LASTFM_API_KEY }}" >> $vars + echo "LASTFM_API_SECRET=${{ secrets.LASTFM_API_SECRET }}" >> $vars ### echo $ADDITIONAL_BUILD_NUMBER diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 00000000000..09201cef499 --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,39 @@ +## Building instructions for Debian 13 + +1. Install necessary software + - `sudo apt install cmake golang gperf meson ninja-build sdkmanager wget yasm` + - `sudo apt install google-android-platform-35-installer google-android-build-tools-35.0.0-installer google-android-ndk-r21e-installer google-android-ndk-r23c-installer openjdk-21-jdk-headless` + +2. Don't forget to include the submodules when you clone: + - `git clone --recursive https://github.com/forkgram/TelegramAndroid.git` + +3. Build native FFmpeg and BoringSSL dependencies: + - Go to the `TMessagesProj/jni` folder and execute the following (define the paths to your NDK and Ninja): + + ``` + export NDK=/usr/lib/android-sdk/ndk/21.4.7075529 + export NINJA_PATH=/usr/bin/ninja + export ANDROID_SDK=/usr/lib/android-sdk + export ANDROID_HOME=/usr/lib/android-sdk + sudo sdkmanager "cmake;3.22.1" --sdk_root /usr/lib/android-sdk + ./build_libvpx_clang.sh + ./build_ffmpeg_clang.sh + ./patch_ffmpeg.sh + ./patch_boringssl.sh + ./build_boringssl.sh + ``` + +4. If you want to publish a modified version of Telegram: + - You should get **your own API key** here: https://core.telegram.org/api/obtaining_api_id and edit a file called `gradle.properties` in the source root directory. + The contents should look like this: + ``` + APP_ID = 12345 + APP_HASH = aaaaaaaabbbbbbccccccfffffff001122 + ``` + - Do not use the name Telegram and the standard logo (white paper plane in a blue circle) for your app — or make sure your users understand that it is unofficial + - Take good care of your users' data and privacy + - **Please remember to publish your code too in order to comply with the licenses** + +The project can be built with Android Studio or from the command line with gradle: + +`./gradlew assembleAfatRelease` \ No newline at end of file diff --git a/README.md b/README.md index d2ebd9af0fc..da7f091675e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,10 @@ Fork Client is a fork of the official Telegram for Android application. - Added ability to see unread count when you want to mark as read multiple dialogs. - Option to directly open the archive on pulldown - PiP mode for YouTube's in-app player +- Added an option to show colored dots to quickly see when a person was last online + - Yellow dot: last seen 15 minutes ago or less + - Orange dot: last seen 30 minutes ago or less + - Red dot: last seen 60 minutes ago or less ### Privacy Features: @@ -48,3 +52,6 @@ Fork Client is a fork of the official Telegram for Android application. ## Downloads: You can download binaries from Releases or from my [Telegram channel Forkgram](https://t.me/forkgram). + +## Building instructions for Debian 13: +[BUILDING.md](BUILDING.md) \ No newline at end of file diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 3fca1b2bb69..47af7a82403 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -7,6 +7,12 @@ apply plugin: 'kotlin-android' repositories { mavenCentral() google() + maven { + url 'https://www.jitpack.io' + content { + includeModule 'com.github.UnifiedPush', 'android-connector' + } + } } configurations { @@ -18,6 +24,8 @@ configurations.all { } dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' + implementation 'androidx.core:core:1.16.0' implementation 'androidx.interpolator:interpolator:1.0.0' implementation 'androidx.fragment:fragment:1.2.0' @@ -55,6 +63,7 @@ dependencies { because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") } } + implementation 'com.github.UnifiedPush:android-connector:2.3.1' implementation 'org.osmdroid:osmdroid-android:6.1.20' } @@ -83,8 +92,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 coreLibraryDesugaringEnabled true } @@ -106,6 +115,8 @@ android { buildConfigField 'String', 'USER_REPO', getStringForConfig(USER_REPO) buildConfigField 'int', 'CHECK_UPDATES', CHECK_UPDATES buildConfigField 'String', 'UPDATE_CHANNEL_USERNAME', getStringForConfig(UPDATE_CHANNEL_USERNAME) + buildConfigField 'String', 'LASTFM_API_KEY', getStringForConfig(LASTFM_API_KEY) + buildConfigField 'String', 'LASTFM_API_SECRET', getStringForConfig(LASTFM_API_SECRET) vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi'] @@ -116,6 +127,7 @@ android { buildConfigField 'boolean', 'SKIP_DNS_RESOLVER', (Utils['isFdroid']() ? 'true' : 'false') buildConfigField 'boolean', 'SKIP_INTERNAL_BROWSER_BY_DEFAULT', (Utils['isFdroid']() ? 'true' : 'false') + manifestPlaceholders = [appLabel: Utils['isFdroid']() ? "@string/AppNameFdroid" : "@string/AppName"] externalNativeBuild { cmake { diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml index 3889435ce68..8d913f2748c 100644 --- a/TMessagesProj/src/main/AndroidManifest.xml +++ b/TMessagesProj/src/main/AndroidManifest.xml @@ -79,7 +79,6 @@ - @@ -639,6 +638,15 @@ e + + + + + + + + + { if (getPushProvider().hasServices()) { @@ -426,7 +425,7 @@ private void initPushServices() { } }, 1000); } - +/* private boolean checkPlayServices() { try { int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java index 29f60ff7a30..bb63f0dcaf5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java @@ -11,8 +11,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.Handler; +import android.os.Looper; import org.telegram.tgnet.TLRPC; +import org.telegram.tgnet.tl.TL_account; public class AutoMessageHeardReceiver extends BroadcastReceiver { @@ -35,6 +38,7 @@ public void onReceive(Context context, Intent intent) { accountInstance.getMessagesController().putUser(user1, true); MessagesController.getInstance(currentAccount).markDialogAsRead(dialogId, maxId, maxId, 0, false, 0, 0, true, 0); MessagesController.getInstance(currentAccount).markReactionsAsRead(dialogId, 0); + scheduleOfflineStatus(currentAccount); }); }); return; @@ -48,6 +52,7 @@ public void onReceive(Context context, Intent intent) { accountInstance.getMessagesController().putChat(chat1, true); MessagesController.getInstance(currentAccount).markDialogAsRead(dialogId, maxId, maxId, 0, false, 0, 0, true, 0); MessagesController.getInstance(currentAccount).markReactionsAsRead(dialogId, 0); + scheduleOfflineStatus(currentAccount); }); }); return; @@ -55,5 +60,25 @@ public void onReceive(Context context, Intent intent) { } MessagesController.getInstance(currentAccount).markDialogAsRead(dialogId, maxId, maxId, 0, false, 0, 0, true, 0); MessagesController.getInstance(currentAccount).markReactionsAsRead(dialogId, 0); + scheduleOfflineStatus(currentAccount); + } + + private void scheduleOfflineStatus(int currentAccount) { + try { + if (!UserConfig.isValidAccount(currentAccount)) { + return; + } + new Handler(Looper.getMainLooper()).postDelayed(() -> { + try { + TL_account.updateStatus req = new TL_account.updateStatus(); + req.offline = true; + org.telegram.tgnet.ConnectionsManager.getInstance(currentAccount).sendRequest(req, null); + } catch (Exception e) { + FileLog.e(e); + } + }, 5000); + } catch (Exception e) { + FileLog.e(e); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index 5b914f3b595..37d7f90168d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -36,6 +36,10 @@ public class BuildVars { public static String HUAWEI_STORE_URL = "https://appgallery.huawei.com/app/C101184875"; public static String GOOGLE_AUTH_CLIENT_ID = "760348033671-81kmi3pi84p11ub8hp9a1funsv0rn2p9.apps.googleusercontent.com"; + // Last.fm API constants + public static String LASTFM_API_KEY = BuildConfig.LASTFM_API_KEY; + public static String LASTFM_API_SECRET = BuildConfig.LASTFM_API_SECRET; + public static String LASTFM_API_URL = "https://ws.audioscrobbler.com/2.0/"; public static String UPDATE_CHANNEL_USERNAME = BuildConfig.UPDATE_CHANNEL_USERNAME; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LastFmHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/LastFmHelper.java new file mode 100644 index 00000000000..17765a96fb9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LastFmHelper.java @@ -0,0 +1,349 @@ +package org.telegram.messenger; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.security.MessageDigest; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +public class LastFmHelper { + + private static LastFmHelper instance; + private SharedPreferences prefs; + private long lastApiCallTime = 0; + private static final long API_CALL_INTERVAL = 30000; // 30 seconds + + public static LastFmHelper getInstance() { + if (instance == null) { + instance = new LastFmHelper(); + } + return instance; + } + + private LastFmHelper() { + if (ApplicationLoader.applicationContext != null) { + prefs = ApplicationLoader.applicationContext.getSharedPreferences("lastfm", Context.MODE_PRIVATE); + } + } + + public boolean isLoggedIn() { + return prefs != null && prefs.getBoolean("logged_in", false); + } + + public String getUsername() { + return prefs != null ? prefs.getString("username", "") : ""; + } + + public void logout() { + if (prefs != null) { + prefs.edit().clear().apply(); + } + } + + public void login(String username, String password, LoginCallback callback) { + Utilities.globalQueue.postRunnable(() -> { + try { + // Step 1: Get token + String token = getToken(); + if (token == null) { + AndroidUtilities.runOnUIThread(() -> callback.onError("Failed to get token")); + return; + } + + // Step 2: Get session + String sessionKey = getSession(username, password, token); + if (sessionKey != null) { + prefs.edit() + .putBoolean("logged_in", true) + .putString("username", username) + .putString("session_key", sessionKey) + .apply(); + AndroidUtilities.runOnUIThread(() -> callback.onSuccess()); + } else { + AndroidUtilities.runOnUIThread(() -> callback.onError("Invalid credentials")); + } + } catch (Exception e) { + FileLog.e(e); + AndroidUtilities.runOnUIThread(() -> callback.onError(e.getMessage())); + } + }); + } + + public interface LoginCallback { + void onSuccess(); + void onError(String error); + } + + public void scrobbleTrack(String artist, String track, String album, long timestamp) { + if (!isLoggedIn() || artist == null || track == null) { + return; + } + + if (!canMakeApiCall()) { + return; + } + + Map params = new HashMap<>(); + params.put("method", "track.scrobble"); + params.put("api_key", BuildVars.LASTFM_API_KEY); + params.put("artist", artist); + params.put("track", track); + if (album != null) { + params.put("album", album); + } + params.put("timestamp", String.valueOf(timestamp)); + params.put("sk", getSessionKey()); + params.put("api_sig", generateApiSignature(params)); + + Utilities.globalQueue.postRunnable(() -> { + try { + sendPostRequestVoid(params); + if (BuildVars.LOGS_ENABLED) { + FileLog.d("Last.fm scrobbled: " + artist + " - " + track); + } + } catch (Exception e) { + FileLog.e(e); + } + }); + } + + public void updateNowPlaying(String artist, String track, String album) { + if (!isLoggedIn() || artist == null || track == null) { + return; + } + + if (!canMakeApiCall()) { + return; + } + + Map params = new HashMap<>(); + params.put("method", "track.updateNowPlaying"); + params.put("api_key", BuildVars.LASTFM_API_KEY); + params.put("artist", artist); + params.put("track", track); + if (album != null) { + params.put("album", album); + } + params.put("sk", getSessionKey()); + params.put("api_sig", generateApiSignature(params)); + + Utilities.globalQueue.postRunnable(() -> { + try { + sendPostRequestVoid(params); + if (BuildVars.LOGS_ENABLED) { + FileLog.d("Last.fm now playing: " + artist + " - " + track); + } + } catch (Exception e) { + FileLog.e(e); + } + }); + } + + private String getSessionKey() { + return prefs != null ? prefs.getString("session_key", "") : ""; + } + + private boolean canMakeApiCall() { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastApiCallTime >= API_CALL_INTERVAL) { + lastApiCallTime = currentTime; + return true; + } + return false; + } + + public String generateApiSignature(Map params) { + TreeMap sortedParams = new TreeMap<>(params); + StringBuilder signature = new StringBuilder(); + + for (Map.Entry entry : sortedParams.entrySet()) { + signature.append(entry.getKey()).append(entry.getValue()); + } + signature.append(BuildVars.LASTFM_API_SECRET); + + return md5(signature.toString()); + } + + private String md5(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(input.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte b : messageDigest) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (Exception e) { + return ""; + } + } + + private String getToken() { + try { + Map params = new HashMap<>(); + params.put("method", "auth.getToken"); + params.put("api_key", BuildVars.LASTFM_API_KEY); + params.put("api_sig", generateApiSignature(params)); + + String response = sendGetRequest(params); + // Parse XML response to get token + // Simplified implementation - real project needs XML parser + if (response != null && response.contains("")) { + int start = response.indexOf("") + 7; + int end = response.indexOf(""); + if (start > 6 && end > start) { + return response.substring(start, end); + } + } + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + private String getSession(String username, String password, String token) { + try { + Map params = new HashMap<>(); + params.put("method", "auth.getMobileSession"); + params.put("api_key", BuildVars.LASTFM_API_KEY); + params.put("username", username); + params.put("password", password); + params.put("api_sig", generateApiSignature(params)); + + String response = sendPostRequest(params); + // Parse XML response to get session key + if (response != null && response.contains("")) { + int start = response.indexOf("") + 5; + int end = response.indexOf(""); + if (start > 4 && end > start) { + return response.substring(start, end); + } + } + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + private String sendGetRequest(Map params) { + try { + StringBuilder urlBuilder = new StringBuilder(BuildVars.LASTFM_API_URL + "?"); + for (Map.Entry entry : params.entrySet()) { + if (urlBuilder.length() > BuildVars.LASTFM_API_URL.length() + 1) { + urlBuilder.append('&'); + } + urlBuilder.append(java.net.URLEncoder.encode(entry.getKey(), "UTF-8")); + urlBuilder.append('='); + urlBuilder.append(java.net.URLEncoder.encode(entry.getValue(), "UTF-8")); + } + + java.net.URL url = new java.net.URL(urlBuilder.toString()); + java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + + int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + java.io.BufferedReader reader = new java.io.BufferedReader( + new java.io.InputStreamReader(connection.getInputStream())); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + connection.disconnect(); + return response.toString(); + } + connection.disconnect(); + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + private String sendPostRequest(Map params) { + try { + java.net.URL url = new java.net.URL(BuildVars.LASTFM_API_URL); + java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + + StringBuilder postData = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (postData.length() != 0) { + postData.append('&'); + } + postData.append(java.net.URLEncoder.encode(entry.getKey(), "UTF-8")); + postData.append('='); + postData.append(java.net.URLEncoder.encode(entry.getValue(), "UTF-8")); + } + + byte[] postDataBytes = postData.toString().getBytes("UTF-8"); + connection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); + connection.getOutputStream().write(postDataBytes); + + int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + java.io.BufferedReader reader = new java.io.BufferedReader( + new java.io.InputStreamReader(connection.getInputStream())); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + connection.disconnect(); + return response.toString(); + } + + if (BuildVars.LOGS_ENABLED) { + FileLog.d("Last.fm API response code: " + responseCode); + } + + connection.disconnect(); + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + private void sendPostRequestVoid(Map params) { + try { + java.net.URL url = new java.net.URL(BuildVars.LASTFM_API_URL); + java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + + StringBuilder postData = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (postData.length() != 0) { + postData.append('&'); + } + postData.append(java.net.URLEncoder.encode(entry.getKey(), "UTF-8")); + postData.append('='); + postData.append(java.net.URLEncoder.encode(entry.getValue(), "UTF-8")); + } + + byte[] postDataBytes = postData.toString().getBytes("UTF-8"); + connection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); + connection.getOutputStream().write(postDataBytes); + + int responseCode = connection.getResponseCode(); + if (BuildVars.LOGS_ENABLED) { + FileLog.d("Last.fm API response code: " + responseCode); + } + + connection.disconnect(); + } catch (Exception e) { + FileLog.e(e); + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 142be2944c8..3003e97bfd8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -3808,6 +3808,18 @@ public void onStateChanged(boolean playWhenReady, int playbackState) { if (playbackState == ExoPlayer.STATE_ENDED || (playbackState == ExoPlayer.STATE_IDLE || playbackState == ExoPlayer.STATE_BUFFERING) && playWhenReady && messageObject.audioProgress >= 0.999f) { messageObject.audioProgress = 1f; NotificationCenter.getInstance(messageObject.currentAccount).postNotificationName(NotificationCenter.messagePlayingProgressDidChanged, messageObject.getId(), 0); + + if (messageObject.isMusic() && messageObject.getDuration() > 30) { + String artist = messageObject.getMusicAuthor(); + String track = messageObject.getMusicTitle(); + String album = null; + if (audioInfo != null && !TextUtils.isEmpty(audioInfo.getAlbum())) { + album = audioInfo.getAlbum(); + } + long timestamp = System.currentTimeMillis() / 1000; + LastFmHelper.getInstance().scrobbleTrack(artist, track, album, timestamp); + } + final boolean restored = restoreMusicPlaylistState(); if (!restored) { if (!playlist.isEmpty() && (playlist.size() > 1 || !messageObject.isVoice())) { @@ -3967,6 +3979,16 @@ public boolean needUpdate() { } startProgressTimer(playingMessageObject); NotificationCenter.getInstance(messageObject.currentAccount).postNotificationName(NotificationCenter.messagePlayingDidStart, messageObject, oldMessageObject); + + if (messageObject.isMusic()) { + String artist = messageObject.getMusicAuthor(); + String track = messageObject.getMusicTitle(); + String album = null; + if (audioInfo != null && !TextUtils.isEmpty(audioInfo.getAlbum())) { + album = audioInfo.getAlbum(); + } + LastFmHelper.getInstance().updateNowPlaying(artist, track, album); + } if (videoPlayer != null) { try { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java index 357ad2ebbc5..adb93f88191 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java @@ -4933,15 +4933,17 @@ public void buildShortcuts() { if (Build.VERSION.SDK_INT < 23) { return; } - int maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(ApplicationLoader.applicationContext) - 2; + int maxShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(ApplicationLoader.applicationContext) - 1; if (maxShortcuts <= 0) { maxShortcuts = 5; } - ArrayList hintsFinal = new ArrayList<>(); - if (SharedConfig.passcodeHash.length() <= 0) { - for (int a = 0; a < hints.size(); a++) { - hintsFinal.add(hints.get(a)); - if (hintsFinal.size() == maxShortcuts - 2) { + + // Get available accounts instead of hints + ArrayList accountsList = new ArrayList<>(); + for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { + if (UserConfig.getInstance(a).isClientActivated()) { + accountsList.add(a); + if (accountsList.size() >= maxShortcuts) { break; } } @@ -4963,10 +4965,8 @@ public void buildShortcuts() { } else { List currentShortcuts = ShortcutManagerCompat.getDynamicShortcuts(ApplicationLoader.applicationContext); if (currentShortcuts != null && !currentShortcuts.isEmpty()) { - newShortcutsIds.add("compose"); - for (int a = 0; a < hintsFinal.size(); a++) { - TLRPC.TL_topPeer hint = hintsFinal.get(a); - newShortcutsIds.add("did3_" + MessageObject.getPeerId(hint.peer)); + for (int a = 0; a < accountsList.size(); a++) { + newShortcutsIds.add("account_" + accountsList.get(a)); } for (int a = 0; a < currentShortcuts.size(); a++) { String id = currentShortcuts.get(a).getId(); @@ -4985,71 +4985,26 @@ public void buildShortcuts() { } } - Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); - intent.setAction("new_dialog"); ArrayList arrayList = new ArrayList<>(); - ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(ApplicationLoader.applicationContext, "compose") - .setShortLabel(LocaleController.getString(R.string.NewConversationShortcut)) - .setLongLabel(LocaleController.getString(R.string.NewConversationShortcut)) - .setIcon(IconCompat.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_compose)) - .setRank(0) - .setIntent(intent) - .build(); - if (recreateShortcuts) { - ShortcutManagerCompat.pushDynamicShortcut(ApplicationLoader.applicationContext, shortcut); - } else { - arrayList.add(shortcut); - if (shortcutsToUpdate.contains("compose")) { - ShortcutManagerCompat.updateShortcuts(ApplicationLoader.applicationContext, arrayList); - } else { - ShortcutManagerCompat.addDynamicShortcuts(ApplicationLoader.applicationContext, arrayList); - } - arrayList.clear(); - } - - HashSet category = new HashSet<>(1); - category.add(SHORTCUT_CATEGORY); - - for (int a = 0; a < hintsFinal.size(); a++) { - Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); - TLRPC.TL_topPeer hint = hintsFinal.get(a); + for (int a = 0; a < accountsList.size(); a++) { + int accountNum = accountsList.get(a); + Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); + shortcutIntent.setAction("switch_account"); + shortcutIntent.putExtra("currentAccount", accountNum); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - TLRPC.User user = null; - TLRPC.Chat chat = null; - long peerId = MessageObject.getPeerId(hint.peer); - if (DialogObject.isUserDialog(peerId)) { - shortcutIntent.putExtra("userId", peerId); - user = getMessagesController().getUser(peerId); - } else { - chat = getMessagesController().getChat(-peerId); - shortcutIntent.putExtra("chatId", -peerId); - } - if ((user == null || UserObject.isDeleted(user)) && chat == null) { + TLRPC.User user = UserConfig.getInstance(accountNum).getCurrentUser(); + if (user == null) { continue; } - String name; + String name = ContactsController.formatName(user.first_name, user.last_name); TLRPC.FileLocation photo = null; - - if (user != null) { - name = ContactsController.formatName(user.first_name, user.last_name); - if (user.photo != null) { - photo = user.photo.photo_small; - } - } else { - name = chat.title; - if (chat.photo != null) { - photo = chat.photo.photo_small; - } + if (user.photo != null) { + photo = user.photo.photo_small; } - shortcutIntent.putExtra("currentAccount", currentAccount); - shortcutIntent.setAction("com.tmessages.openchat" + peerId); - shortcutIntent.putExtra("dialogId", peerId); - shortcutIntent.putExtra("hash", SharedConfig.directShareHash); - shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - Bitmap bitmap = null; if (photo != null) { try { @@ -5083,18 +5038,16 @@ public void buildShortcuts() { } } - String id = "did3_" + peerId; + String id = "account_" + accountNum; if (TextUtils.isEmpty(name)) { - name = " "; + name = "Account " + (accountNum + 1); } ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(ApplicationLoader.applicationContext, id) .setShortLabel(name) .setLongLabel(name) - .setRank(1 + a) + .setRank(a) .setIntent(shortcutIntent); - if (SharedConfig.directShare) { - builder.setCategories(category); - } + if (bitmap != null) { builder.setIcon(IconCompat.createWithBitmap(bitmap)); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java b/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java index 780c255e1bc..38f68653f88 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/PushListenerController.java @@ -20,21 +20,26 @@ import org.telegram.tgnet.SerializedData; import org.telegram.tgnet.TLRPC; +import org.unifiedpush.android.connector.UnifiedPush; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; @Keep public class PushListenerController { public static final int PUSH_TYPE_FIREBASE = 2, + PUSH_TYPE_SIMPLE = 4, PUSH_TYPE_HUAWEI = 13; @Retention(RetentionPolicy.SOURCE) @IntDef({ PUSH_TYPE_FIREBASE, + PUSH_TYPE_SIMPLE, PUSH_TYPE_HUAWEI }) public @interface PushType {} @@ -62,7 +67,7 @@ public static void sendRegistrationToServer(@PushType int pushType, String token if (userConfig.getClientUserId() != 0) { final int currentAccount = a; if (sendStat) { - String tag = pushType == PUSH_TYPE_FIREBASE ? "fcm" : "hcm"; + String tag = pushType == PUSH_TYPE_FIREBASE ? "fcm" : (pushType == PUSH_TYPE_HUAWEI ? "hcm" : "up"); TLRPC.TL_help_saveAppLog req = new TLRPC.TL_help_saveAppLog(); TLRPC.TL_inputAppEvent event = new TLRPC.TL_inputAppEvent(); event.time = SharedConfig.pushStringGetTimeStart; @@ -90,7 +95,7 @@ public static void sendRegistrationToServer(@PushType int pushType, String token } public static void processRemoteMessage(@PushType int pushType, String data, long time) { - String tag = pushType == PUSH_TYPE_FIREBASE ? "FCM" : "HCM"; + String tag = pushType == PUSH_TYPE_FIREBASE ? "FCM" : (pushType == PUSH_TYPE_HUAWEI ? "HCM" : "UP"); if (BuildVars.LOGS_ENABLED) { FileLog.d(tag + " PRE START PROCESSING"); } @@ -1689,4 +1694,64 @@ public boolean hasServices() { return hasServices;*/ } } + public final static class UnifiedPushListenerServiceProvider implements IPushListenerServiceProvider { + public final static UnifiedPushListenerServiceProvider INSTANCE = new UnifiedPushListenerServiceProvider(); + private final static UnifiedPushReceiver mReceiver = new UnifiedPushReceiver(); + + private UnifiedPushListenerServiceProvider(){}; + + @Override + public boolean hasServices() { + return !UnifiedPush.getDistributors(ApplicationLoader.applicationContext, new ArrayList()).isEmpty(); + } + + @Override + public String getLogTitle() { + return "UnifiedPush"; + } + + @Override + public void onRequestPushToken() { + if (SharedConfig.disableUnifiedPush) { + UnifiedPush.unregisterApp(ApplicationLoader.applicationContext, "default"); + } else { + String currentPushString = SharedConfig.pushString; + if (!TextUtils.isEmpty(currentPushString)) { + if (BuildVars.DEBUG_PRIVATE_VERSION && BuildVars.LOGS_ENABLED) { + FileLog.d("UnifiedPush endpoint = " + currentPushString); + } + } else { + if (BuildVars.LOGS_ENABLED) { + FileLog.d("No UnifiedPush string found"); + } + } + Utilities.globalQueue.postRunnable(() -> { + try { + SharedConfig.pushStringGetTimeStart = SystemClock.elapsedRealtime(); + SharedConfig.saveConfig(); + if (UnifiedPush.getAckDistributor(ApplicationLoader.applicationContext) == null) { + List distributors = UnifiedPush.getDistributors(ApplicationLoader.applicationContext, new ArrayList<>()); + if (distributors.size() > 0) { + String distributor = distributors.get(0); + UnifiedPush.saveDistributor(ApplicationLoader.applicationContext, distributor); + } + } + UnifiedPush.registerApp( + ApplicationLoader.applicationContext, + "default", + new ArrayList<>(), + "Telegram Simple Push" + ); + } catch (Throwable e) { + FileLog.e(e); + } + }); + } + } + + @Override + public int getPushType() { + return PUSH_TYPE_SIMPLE; + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java index d993f36ec7f..a84aca8acc4 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SharedConfig.java @@ -144,6 +144,14 @@ public static void togglePaymentByInvoice() { .apply(); } + public static void setUnifiedPushGateway(String value) { + unifiedPushGateway = value; + ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE) + .edit() + .putString("unifiedPushGateway", unifiedPushGateway) + .apply(); + } + public static void toggleSurfaceInStories() { useSurfaceInStories = !useSurfaceInStories; ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE) @@ -262,6 +270,8 @@ private static boolean isWhitelisted(MediaCodecInfo codecInfo) { public static boolean useSurfaceInStories; public static boolean photoViewerBlur = true; public static boolean payByInvoice; + public static boolean disableUnifiedPush; + public static String unifiedPushGateway; public static int stealthModeSendMessageConfirm = 2; private static int lastLocalId = -210000; @@ -674,6 +684,8 @@ public static void loadConfig() { multipleReactionsPromoShowed = preferences.getBoolean("multipleReactionsPromoShowed", false); callEncryptionHintDisplayedCount = preferences.getInt("callEncryptionHintDisplayedCount", 0); debugVideoQualities = preferences.getBoolean("debugVideoQualities", false); + disableUnifiedPush = preferences.getBoolean("disableUnifiedPush", false); + unifiedPushGateway = preferences.getString("unifiedPushGateway", "https://p2p.belloworld.it/"); loadDebugConfig(preferences); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UnifiedPushReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/UnifiedPushReceiver.java new file mode 100644 index 00000000000..2f401e3db2e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UnifiedPushReceiver.java @@ -0,0 +1,109 @@ +package org.telegram.messenger; + +import android.content.Context; +import android.os.SystemClock; + +import org.telegram.tgnet.ConnectionsManager; +import org.unifiedpush.android.connector.MessagingReceiver; +import org.unifiedpush.android.connector.UnifiedPush; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.concurrent.CountDownLatch; + +public class UnifiedPushReceiver extends MessagingReceiver { + + private static long lastReceivedNotification = 0; + private static long numOfReceivedNotifications = 0; + + public static long getLastReceivedNotification() { + return lastReceivedNotification; + } + + public static long getNumOfReceivedNotifications() { + return numOfReceivedNotifications; + } + + @Override + public void onNewEndpoint(Context context, String endpoint, String instance){ + Utilities.globalQueue.postRunnable(() -> { + SharedConfig.pushStringGetTimeEnd = SystemClock.elapsedRealtime(); + + String savedDistributor = UnifiedPush.getSavedDistributor(context); + + if (savedDistributor.equals("io.heckel.ntfy")) { + PushListenerController.sendRegistrationToServer(PushListenerController.PUSH_TYPE_SIMPLE, endpoint); + } else { + try { + PushListenerController.sendRegistrationToServer(PushListenerController.PUSH_TYPE_SIMPLE, SharedConfig.unifiedPushGateway + URLEncoder.encode(endpoint, "UTF-8")); + } catch (UnsupportedEncodingException e) { + FileLog.e(e); + } + } + }); + } + + @Override + public void onMessage(Context context, byte[] message, String instance){ + final long receiveTime = SystemClock.elapsedRealtime(); + final CountDownLatch countDownLatch = new CountDownLatch(1); + + lastReceivedNotification = SystemClock.elapsedRealtime(); + numOfReceivedNotifications++; + + AndroidUtilities.runOnUIThread(() -> { + if (BuildVars.LOGS_ENABLED) { + FileLog.d("UP PRE INIT APP"); + } + ApplicationLoader.postInitApplication(); + if (BuildVars.LOGS_ENABLED) { + FileLog.d("UP POST INIT APP"); + } + Utilities.stageQueue.postRunnable(() -> { + if (BuildVars.LOGS_ENABLED) { + FileLog.d("UP START PROCESSING"); + } + for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) { + if (UserConfig.getInstance(a).isClientActivated()) { + ConnectionsManager.onInternalPushReceived(a); + ConnectionsManager.getInstance(a).resumeNetworkMaybe(); + } + } + countDownLatch.countDown(); + }); + }); + Utilities.globalQueue.postRunnable(()-> { + try { + countDownLatch.await(); + } catch (Throwable ignore) { + + } + if (BuildVars.DEBUG_VERSION) { + FileLog.d("finished UP service, time = " + (SystemClock.elapsedRealtime() - receiveTime)); + } + }); + } + + @Override + public void onRegistrationFailed(Context context, String instance){ + if (BuildVars.LOGS_ENABLED) { + FileLog.d("Failed to get endpoint"); + } + SharedConfig.pushStringStatus = "__UNIFIEDPUSH_FAILED__"; + Utilities.globalQueue.postRunnable(() -> { + SharedConfig.pushStringGetTimeEnd = SystemClock.elapsedRealtime(); + + PushListenerController.sendRegistrationToServer(PushListenerController.PUSH_TYPE_SIMPLE, null); + }); + } + + @Override + public void onUnregistered(Context context, String instance){ + SharedConfig.pushStringStatus = "__UNIFIEDPUSH_FAILED__"; + Utilities.globalQueue.postRunnable(() -> { + SharedConfig.pushStringGetTimeEnd = SystemClock.elapsedRealtime(); + + PushListenerController.sendRegistrationToServer(PushListenerController.PUSH_TYPE_SIMPLE, null); + }); + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/AppUpdater.kt b/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/AppUpdater.kt index 282e13223cb..f4ac947fa27 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/AppUpdater.kt +++ b/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/AppUpdater.kt @@ -32,7 +32,7 @@ import java.net.HttpURLConnection import java.net.URL object AppUpdater { - private const val kCheckInterval = 30 * 60 * 1000 // 30 minutes. + private const val title = "The latest Forkgram version" private const val desc = "" private const val PREFS_NAME = "AppUpdaterPrefs" @@ -85,7 +85,8 @@ object AppUpdater { manual: Boolean = false) { try { - if (!manual && System.currentTimeMillis() - lastTimestampOfCheck < kCheckInterval) { + val updateInterval = MessagesController.getGlobalMainSettings().getLong("updateForkCheckInterval", 30 * 60 * 1000L) + if (!manual && (updateInterval == 0L || System.currentTimeMillis() - lastTimestampOfCheck < updateInterval)) { return } if (downloadId != 0L) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/Dialogs.kt b/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/Dialogs.kt index 06b753af6cf..33b81bc3419 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/Dialogs.kt +++ b/TMessagesProj/src/main/java/org/telegram/messenger/forkgram/Dialogs.kt @@ -231,6 +231,101 @@ public fun CreateDeleteAllUnpinnedMessagesAlert( } } +@JvmStatic +public fun CreateDeleteAllYourMessagesInAllTopicsAlert( + currentAccount: Int, + dialogId: Long, + context: Context) { + + val create = { text: String, callback: () -> Unit -> + val builder = AlertDialog.Builder(context); + builder.setTitle(LocaleController.getString( + "DeleteAllYourMessages", + R.string.DeleteAllYourMessages)); + builder.setMessage(AndroidUtilities.replaceTags(text)); + + builder.setPositiveButton( + LocaleController.getString("OK", R.string.OK), + { _: DialogInterface?, _: Int -> callback(); }); + builder.setNegativeButton( + LocaleController.getString("Cancel", R.string.Cancel), + null); + val dialog = builder.show(); + val button = dialog.getButton(DialogInterface.BUTTON_POSITIVE) as TextView; + button.setTextColor(Theme.getColor(Theme.key_text_RedBold)); + }; + + val messagesController = AccountInstance.getInstance(currentAccount).messagesController; + val topicsController = messagesController.topicsController; + val meId = UserConfig.getInstance(UserConfig.selectedAccount).clientUserId; + val chatId = -dialogId; + + val deleteFor = { to: Long, found: ArrayList -> + val messages: java.util.ArrayList = ArrayList(found.map { it.id }); + AndroidUtilities.runOnUIThread { + messagesController.deleteMessages( + messages, + null, + null, + to, + 0, + true, + 0); + }; + }; + + create( + LocaleController.getString( + "DeleteAllYourMessagesInfo", + R.string.DeleteAllYourMessagesInfo) + "\n\n" + + "This will delete your messages in ALL topics of this group." + ) { + create(LocaleController.getString("ReallySure", R.string.ReallySure)) { + val topics = topicsController.getTopics(chatId); + val mePeer = messagesController.getInputPeer(meId); + val dialogPeer = messagesController.getInputPeer(dialogId); + + if (topics != null && topics.isNotEmpty()) { + // Delete messages in each topic + for (topic in topics) { + // Use the main dialog peer but search within specific topic + ForkApi.SearchAllMessages( + currentAccount, + dialogPeer, + mePeer, + { found: ArrayList -> + // Filter messages that belong to this topic + val topicMessages = found.filter { message -> + val topicId = if (message.reply_to != null && message.reply_to.reply_to_top_id != 0) { + message.reply_to.reply_to_top_id + } else if (message.reply_to != null && message.reply_to.reply_to_msg_id != 0) { + message.reply_to.reply_to_msg_id + } else { + message.id + } + topicId == topic.id + } + if (topicMessages.isNotEmpty()) { + deleteFor(dialogId, ArrayList(topicMessages)) + } + }, + {} + ); + } + } else { + // Fallback to main dialog if no topics + ForkApi.SearchAllMessages( + currentAccount, + dialogPeer, + mePeer, + { found: ArrayList -> deleteFor(dialogId, found); }, + {} + ); + } + } + } +} + @JvmStatic public fun CreateFieldAlert( context: Context, diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java index 6f7ea6048ec..f21af71845d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -150,6 +150,8 @@ import java.util.Objects; import java.util.concurrent.CountDownLatch; +import tw.nekomimi.nekogram.helpers.MonetHelper; + public class Theme { public static final String DEFAULT_BACKGROUND_SLUG = "d"; @@ -2506,13 +2508,17 @@ public boolean hasAccentColors() { return defaultAccentCount != 0; } + public boolean isMonet() { + return "Monet Dark".equals(name) || "Monet Light".equals(name); + } + public boolean isDark() { if (isDark != UNKNOWN) { return isDark == DARK; } - if ("Dark Blue".equals(name) || "Night".equals(name)) { + if ("Dark Blue".equals(name) || "Night".equals(name) || "Monet Dark".equals(name)) { isDark = DARK; - } else if ("Blue".equals(name) || "Arctic Blue".equals(name) || "Day".equals(name)) { + } else if ("Blue".equals(name) || "Arctic Blue".equals(name) || "Day".equals(name) || "Monet Light".equals(name)) { isDark = LIGHT; } if (isDark == UNKNOWN) { @@ -4727,6 +4733,28 @@ public void run() { themes.add(themeInfo); themesDict.put("Night", themeInfo); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + themeInfo = new ThemeInfo(); + themeInfo.name = "Monet Light"; + themeInfo.assetName = "monet_light.attheme"; + themeInfo.previewBackgroundColor = MonetHelper.getColor("n1_50"); + themeInfo.previewInColor = MonetHelper.getColor("a1_100"); + themeInfo.previewOutColor = MonetHelper.getColor("a1_600"); + themeInfo.sortIndex = 6; + themes.add(themeInfo); + themesDict.put("Monet Light", themeInfo); + + themeInfo = new ThemeInfo(); + themeInfo.name = "Monet Dark"; + themeInfo.assetName = "monet_dark.attheme"; + themeInfo.previewBackgroundColor = MonetHelper.getColor("n1_900"); + themeInfo.previewInColor = MonetHelper.getColor("n2_800"); + themeInfo.previewOutColor = MonetHelper.getColor("a1_100"); + themeInfo.sortIndex = 7; + themes.add(themeInfo); + themesDict.put("Monet Dark", themeInfo); + } + String themesString = themeConfig.getString("themes2", null); int remoteVersion = themeConfig.getInt("remote_version", 0); @@ -4959,6 +4987,8 @@ public void run() { if (accent != null) { info.overrideWallpaper = accent.overrideWallpaper; } + } else if (info.isMonet()) { + info.loadWallpapers(themeConfig); } } if (oldEditor != null) { @@ -8123,6 +8153,8 @@ public static SparseIntArray getThemeFileValues(File file, String assetName, Str } catch (Exception ignore) { value = Utilities.parseInt(param); } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && (param.startsWith("a") || param.startsWith("n"))) { + value = MonetHelper.getColor(param.trim()); } else { value = Utilities.parseInt(param); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index 2f7eb749343..57f579af8db 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -4665,6 +4665,8 @@ public boolean drawAvatarOverlays(Canvas canvas) { final int diff = user.status.expires - ConnectionsManager.getInstance(currentAccount).getCurrentTime(); colorOnline = diff > 0 ? Theme.getColor(Theme.key_chats_onlineCircle) + : !MessagesController.getGlobalMainSettings().getBoolean("enableLastSeenDots", true) + ? 0 : diff > -15 * 60 ? android.graphics.Color.argb(255, 234, 234, 30) : diff > -30 * 60 diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index cea3c71048f..f08ab43c71f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -1714,6 +1714,7 @@ public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo, boolea private final static int goToFirstMessage = 35; private final static int deleteAllYourMessages = 36; private final static int deleteAllUnpinnedMessages = 37; + private final static int deleteAllYourMessagesInAllTopics = 38; private final static int attach_photo = 0; private final static int attach_gallery = 1; @@ -4191,6 +4192,11 @@ public void run(boolean revoke) { currentAccount, dialog_id, getParentActivity()); + } else if (id == deleteAllYourMessagesInAllTopics) { + org.telegram.messenger.forkgram.ForkDialogs.CreateDeleteAllYourMessagesInAllTopicsAlert( + currentAccount, + dialog_id, + getParentActivity()); } else if (id == deleteAllUnpinnedMessages) { org.telegram.messenger.forkgram.ForkDialogs.CreateDeleteAllUnpinnedMessagesAlert( currentAccount, @@ -4531,6 +4537,14 @@ public void toggleMute() { R.drawable.msg_delete, LocaleController.getString("DeleteAllYourMessages", R.string.DeleteAllYourMessages), themeDelegate); + // Add option to delete messages in all topics for forum groups + if (ChatObject.isForum(currentChat)) { + headerItem.addSubItem( + deleteAllYourMessagesInAllTopics, + R.drawable.msg_delete, + LocaleController.getString("DeleteAllYourMessagesInAllTopics", R.string.DeleteAllYourMessagesInAllTopics), + themeDelegate); + } } if (MessagesController.getGlobalMainSettings().getBoolean("addItemToDeleteAllUnpinnedMessages", false) && ((currentUser != null && currentEncryptedChat == null) || currentChat != null)) { @@ -29974,6 +29988,9 @@ public boolean maybePlayVisibleVideo() { if (chatListView == null) { return false; } + if (org.telegram.messenger.MessagesController.getGlobalMainSettings().getBoolean("disablePlayVisibleVideoOnVolume", false)) { + return false; + } MessageObject playingMessage = MediaController.getInstance().getPlayingMessageObject(); if (playingMessage != null && !playingMessage.isVideo()) { return false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java index b156b001869..79e6c8b2557 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContentPreviewViewer.java @@ -1590,7 +1590,7 @@ public void open(TLRPC.Document document, SendMessagesHelper.ImportingSticker st } if ((newSet != null || contentType == CONTENT_TYPE_EMOJI) && (delegate == null || delegate.needMenu())) { AndroidUtilities.cancelRunOnUIThread(showSheetRunnable); - AndroidUtilities.runOnUIThread(showSheetRunnable, 1300); + AndroidUtilities.runOnUIThread(showSheetRunnable, 700); } TLRPC.TL_messages_stickerSet stickerSet = MediaDataController.getInstance(currentAccount).getStickerSet(newSet, true); if (stickerSet != null && stickerSet.documents.isEmpty()) { @@ -1641,7 +1641,7 @@ public void open(TLRPC.Document document, SendMessagesHelper.ImportingSticker st } if (delegate.needMenu()) { AndroidUtilities.cancelRunOnUIThread(showSheetRunnable); - AndroidUtilities.runOnUIThread(showSheetRunnable, 1300); + AndroidUtilities.runOnUIThread(showSheetRunnable, 700); } } } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index 9619565ac53..a880e733f1d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -12959,7 +12959,8 @@ public void updateStoriesVisibility(boolean animated) { boolean onlySelfStories = !isArchive() && getStoriesController().hasOnlySelfStories(); boolean newVisibility; if (isArchive()) { - newVisibility = !getStoriesController().getHiddenList().isEmpty(); + boolean hideStoriesInArchive = MessagesController.getGlobalMainSettings().getBoolean("hideStoriesInArchive", false); + newVisibility = !hideStoriesInArchive && !getStoriesController().getHiddenList().isEmpty(); } else { newVisibility = !onlySelfStories && getStoriesController().hasStories(); onlySelfStories = getStoriesController().hasOnlySelfStories(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ForkSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ForkSettingsActivity.java index 0ce5b63b7cd..1d66b7dfcda 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ForkSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ForkSettingsActivity.java @@ -18,6 +18,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BuildVars; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; @@ -131,8 +132,8 @@ public void invalidate() { private ListAdapter listAdapter; private ArrayList sectionRows = new ArrayList(); - private String[] sectionStrings = {"General", "ChatList", "FilterChats", "ChatCamera", "StickerSize"}; - private int[] sectionInts = {0, R.string.ChatList, R.string.FilterChats, 0, R.string.StickerSize}; + private String[] sectionStrings = {"General", "ChatList", "FilterChats", "ChatCamera", "StickerSize", "ThirdParty"}; + private int[] sectionInts = {0, R.string.ChatList, R.string.FilterChats, 0, R.string.StickerSize, R.string.ThirdParty}; private int rowCount; @@ -152,6 +153,7 @@ public void invalidate() { private int formatWithSeconds; private int disableThumbsInDialogList; private int disableGlobalSearch; + private int enableLastSeenDots; private int customTitleRow; private int fullRecentStickersRow; private int hideSendAsRow; @@ -165,12 +167,18 @@ public void invalidate() { private int botSkipShare; private int botSkipFullscreen; private int disableDefaultInAppBrowser; + private int hideStoriesInArchiveRow; + private int disablePlayVisibleVideoOnVolumeRow; + private int updateCheckIntervalRow; + private int lastFmLoginRow; private int stickerSizeRow; private ArrayList emptyRows = new ArrayList(); private int syncPinsRow; + private int disableUnifiedPushRow; + private static int getIntLocale(String str) { try { try { @@ -196,6 +204,81 @@ private static String getLocale(String s, int i) { return LocaleController.getString(s, i); } + private String getUpdateIntervalText() { + SharedPreferences preferences = MessagesController.getGlobalMainSettings(); + long interval = preferences.getLong("updateForkCheckInterval", 30 * 60 * 1000); + + if (interval == 0) { + return "Disabled"; + } else if (interval < 60 * 1000) { + return (interval / 1000) + " sec"; + } else if (interval < 60 * 60 * 1000) { + return (interval / (60 * 1000)) + " min"; + } else if (interval < 24 * 60 * 60 * 1000) { + return (interval / (60 * 60 * 1000)) + " h"; + } else { + return (interval / (24 * 60 * 60 * 1000)) + " d"; + } + } + + private void showUpdateIntervalDialog() { + String[] options = { + "Disabled", + "5 minutes", + "15 minutes", + "30 minutes", + "1 hour", + "2 hours", + "6 hours", + "12 hours", + "24 hours", + "2 days", + "7 days" + }; + + long[] intervals = { + 0, + 5 * 60 * 1000, + 15 * 60 * 1000, + 30 * 60 * 1000, + 60 * 60 * 1000, + 2 * 60 * 60 * 1000, + 6 * 60 * 60 * 1000, + 12 * 60 * 60 * 1000, + 24 * 60 * 60 * 1000, + 2 * 24 * 60 * 60 * 1000, + 7 * 24 * 60 * 60 * 1000 + }; + + SharedPreferences preferences = MessagesController.getGlobalMainSettings(); + long currentInterval = preferences.getLong("updateForkCheckInterval", 30 * 60 * 1000); + + int selectedIndex = 3; // default 30 minutes + for (int i = 0; i < intervals.length; i++) { + if (intervals[i] == currentInterval) { + selectedIndex = i; + break; + } + } + + android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(getParentActivity()); + builder.setTitle("Update Check Interval"); + builder.setSingleChoiceItems(options, selectedIndex, (dialog, which) -> { + SharedPreferences.Editor editor = preferences.edit(); + editor.putLong("updateForkCheckInterval", intervals[which]); + editor.commit(); + + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findViewHolderForAdapterPosition(updateCheckIntervalRow); + if (holder != null && holder.itemView instanceof TextSettingsCell) { + ((TextSettingsCell) holder.itemView).getValueTextView().setText(getUpdateIntervalText()); + } + + dialog.dismiss(); + }); + builder.setNegativeButton("Cancel", null); + builder.show(); + } + @Override public boolean onFragmentCreate() { super.onFragmentCreate(); @@ -209,15 +292,19 @@ public boolean onFragmentCreate() { showNotificationContent = rowCount++; hideBottomButton = SharedConfig.isUserOwner() ? rowCount++ : -1; lockPremium = rowCount++; + disableUnifiedPushRow = rowCount++; emptyRows.add(rowCount++); sectionRows.add(rowCount++); syncPinsRow = rowCount++; unmutedOnTopRow = rowCount++; openArchiveOnPull = rowCount++; + hideStoriesInArchiveRow = rowCount++; disableThumbsInDialogList = rowCount++; disableGlobalSearch = rowCount++; + enableLastSeenDots = rowCount++; customTitleRow = rowCount++; + updateCheckIntervalRow = rowCount++; emptyRows.add(rowCount++); sectionRows.add(rowCount++); @@ -235,6 +322,7 @@ public boolean onFragmentCreate() { disableSlideToNextChannel = rowCount++; disableRecentFilesAttachment = rowCount++; disableDefaultInAppBrowser = rowCount++; + disablePlayVisibleVideoOnVolumeRow = rowCount++; emptyRows.add(rowCount++); botSkipShare = rowCount++; @@ -249,6 +337,11 @@ public boolean onFragmentCreate() { sectionRows.add(rowCount++); stickerSizeRow = rowCount++; + emptyRows.add(rowCount++); + sectionRows.add(rowCount++); + lastFmLoginRow = (BuildVars.LASTFM_API_KEY != null && BuildVars.LASTFM_API_KEY.length() > 2 && + BuildVars.LASTFM_API_SECRET != null && BuildVars.LASTFM_API_SECRET.length() > 2) ? rowCount++ : -1; + return true; } @@ -351,6 +444,8 @@ public boolean supportsPredictiveItemAnimations() { toggleGlobalMainSetting("disableRecentFilesAttachment", view, false); } else if (position == disableDefaultInAppBrowser) { toggleGlobalMainSetting("disableDefaultInAppBrowser", view, false); + } else if (position == disablePlayVisibleVideoOnVolumeRow) { + toggleGlobalMainSetting("disablePlayVisibleVideoOnVolume", view, false); } else if (position == botSkipShare) { toggleGlobalMainSetting("botSkipShare", view, false); } else if (position == botSkipFullscreen) { @@ -363,6 +458,8 @@ public boolean supportsPredictiveItemAnimations() { toggleGlobalMainSetting("mentionByName", view, false); } else if (position == openArchiveOnPull) { toggleGlobalMainSetting("openArchiveOnPull", view, false); + } else if (position == hideStoriesInArchiveRow) { + toggleGlobalMainSetting("hideStoriesInArchive", view, false); } else if (position == disableFlipPhotos) { toggleGlobalMainSetting("disableFlipPhotos", view, false); } else if (position == formatWithSeconds) { @@ -371,6 +468,8 @@ public boolean supportsPredictiveItemAnimations() { toggleGlobalMainSetting("disableThumbsInDialogList", view, false); } else if (position == disableGlobalSearch) { toggleGlobalMainSetting("disableGlobalSearch", view, false); + } else if (position == enableLastSeenDots) { + toggleGlobalMainSetting("enableLastSeenDots", view, true); } else if (position == hideBottomButton) { toggleGlobalMainSetting("hideBottomButton", view, false); } else if (position == syncPinsRow) { @@ -402,6 +501,12 @@ public boolean supportsPredictiveItemAnimations() { } return null; }); + } else if (position == updateCheckIntervalRow) { + showUpdateIntervalDialog(); + } else if (position == lastFmLoginRow) { + presentFragment(new LastFmLoginActivity()); + } else if (position == disableUnifiedPushRow) { + toggleGlobalMainSetting("disableUnifiedPush", view, false); } }); @@ -438,6 +543,12 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { String t = LocaleController.getString("EditAdminRank", R.string.EditAdminRank); final String v = MessagesController.getGlobalMainSettings().getString("forkCustomTitle", "Fork Client"); textCell.setTextAndValue(t, v, false); + } else if (position == updateCheckIntervalRow) { + String t = "Update Check Interval"; + String v = getUpdateIntervalText(); + textCell.setTextAndValue(t, v, false); + } else if (position == lastFmLoginRow) { + textCell.setTextAndIcon("Last.fm Login", R.drawable.ic_lastfm, false); } break; } @@ -500,6 +611,9 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } else if (position == disableDefaultInAppBrowser) { String t = LocaleController.getString("DisableDefaultInAppBrowser", R.string.DisableDefaultInAppBrowser); textCell.setTextAndCheck(t, preferences.getBoolean("disableDefaultInAppBrowser", false), false); + } else if (position == disablePlayVisibleVideoOnVolumeRow) { + String t = "Disable play video on volume change"; + textCell.setTextAndCheck(t, preferences.getBoolean("disablePlayVisibleVideoOnVolume", false), false); } else if (position == botSkipShare) { String t = LocaleController.getString("BotSkipShare", R.string.BotSkipShare); textCell.setTextAndCheck(t, preferences.getBoolean("botSkipShare", false), false); @@ -519,6 +633,9 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } else if (position == openArchiveOnPull) { String t = LocaleController.getString("OpenArchiveOnPull", R.string.OpenArchiveOnPull); textCell.setTextAndCheck(t, preferences.getBoolean("openArchiveOnPull", true), false); + } else if (position == hideStoriesInArchiveRow) { + String t = LocaleController.getString("HideStoriesInArchive", R.string.HideStoriesInArchive); + textCell.setTextAndCheck(t, preferences.getBoolean("hideStoriesInArchive", false), false); } else if (position == disableFlipPhotos) { String t = LocaleController.getString("DisableFlipPhotos", R.string.DisableFlipPhotos); textCell.setTextAndCheck(t, preferences.getBoolean("disableFlipPhotos", false), false); @@ -531,6 +648,9 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } else if (position == disableGlobalSearch) { String t = LocaleController.getString("DisableGlobalSearch", R.string.DisableGlobalSearch); textCell.setTextAndCheck(t, preferences.getBoolean("disableGlobalSearch", false), false); + } else if (position == enableLastSeenDots) { + String t = "Enable last seen colored dots"; + textCell.setTextAndCheck(t, preferences.getBoolean("enableLastSeenDots", true), false); } else if (position == hideBottomButton) { String t = LocaleController.getString("HideBottomButton", R.string.HideBottomButton); textCell.setTextAndCheck(t, preferences.getBoolean("hideBottomButton", false), false); @@ -542,6 +662,10 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { String t = LocaleController.getString("HideSensitiveData", R.string.HideSensitiveData); String info = LocaleController.getString("SquareAvatarsInfo", R.string.SquareAvatarsInfo); textCell.setTextAndValueAndCheck(t, info, preferences.getBoolean("hideSensitiveData", false), true, false); + } else if (position == disableUnifiedPushRow) { + String t = LocaleController.getString("DisableUnifiedPush", R.string.DisableUnifiedPush); + String info = LocaleController.getString("DisableUnifiedPushInfo", R.string.DisableUnifiedPushInfo); + textCell.setTextAndValueAndCheck(t, info, preferences.getBoolean("disableUnifiedPush", true), true, false); } break; } @@ -575,21 +699,27 @@ public boolean isEnabled(RecyclerView.ViewHolder holder) { || position == disableSlideToNextChannel || position == disableRecentFilesAttachment || position == disableDefaultInAppBrowser + || position == disablePlayVisibleVideoOnVolumeRow || position == botSkipShare || position == botSkipFullscreen || position == lockPremium || position == replaceForward || position == mentionByName || position == openArchiveOnPull + || position == hideStoriesInArchiveRow || position == disableFlipPhotos || position == formatWithSeconds || position == disableThumbsInDialogList || position == disableGlobalSearch + || position == enableLastSeenDots || position == customTitleRow || position == hideBottomButton || position == syncPinsRow || position == showNotificationContent - || position == photoHasStickerRow; + || position == photoHasStickerRow + || position == updateCheckIntervalRow + || position == lastFmLoginRow + || position == disableUnifiedPushRow; return fork; } @@ -627,7 +757,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType public int getItemViewType(int position) { if (emptyRows.contains(position)) { return 1; - } else if (position == customTitleRow) { + } else if (position == customTitleRow || position == updateCheckIntervalRow || position == lastFmLoginRow) { return 2; } else if (position == squareAvatarsRow || position == hideSensitiveDataRow @@ -645,19 +775,23 @@ public int getItemViewType(int position) { || position == disableSlideToNextChannel || position == disableRecentFilesAttachment || position == disableDefaultInAppBrowser + || position == disablePlayVisibleVideoOnVolumeRow || position == botSkipShare || position == botSkipFullscreen || position == lockPremium || position == replaceForward || position == mentionByName || position == openArchiveOnPull + || position == hideStoriesInArchiveRow || position == disableFlipPhotos || position == formatWithSeconds || position == disableThumbsInDialogList || position == disableGlobalSearch + || position == enableLastSeenDots || position == hideBottomButton || position == showNotificationContent - || position == photoHasStickerRow) { + || position == photoHasStickerRow + || position == disableUnifiedPushRow) { return 3; } else if (sectionRows.contains(position)) { return 4; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LastFmLoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LastFmLoginActivity.java new file mode 100644 index 00000000000..b54158926a7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/LastFmLoginActivity.java @@ -0,0 +1,271 @@ +package org.telegram.ui; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BuildVars; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.MessageDigest; +import java.util.TreeMap; + +public class LastFmLoginActivity extends BaseFragment { + + private static final String API_KEY = BuildVars.LASTFM_API_KEY; + private static final String API_SECRET = BuildVars.LASTFM_API_SECRET; + private TextView loginButton; + private TextView statusText; + private boolean waitingForAuth = false; + private boolean isLoggedIn = false; + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle("Last.fm Login"); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + checkLoginStatus(); + + fragmentView = new LinearLayout(context); + LinearLayout linearLayout = (LinearLayout) fragmentView; + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + TextView headerText = new TextView(context); + headerText.setText("Connect your Last.fm account"); + headerText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + headerText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + headerText.setGravity(Gravity.CENTER); + linearLayout.addView(headerText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 24, 24, 12)); + + TextView descText = new TextView(context); + descText.setText("Get authorization token from Last.fm"); + descText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + descText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + descText.setGravity(Gravity.CENTER); + linearLayout.addView(descText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 12, 24, 12)); + + statusText = new TextView(context); + statusText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + statusText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + statusText.setGravity(Gravity.CENTER); + linearLayout.addView(statusText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 12, 24, 12)); + + loginButton = new TextView(context); + loginButton.setPadding(AndroidUtilities.dp(34), 0, AndroidUtilities.dp(34), 0); + loginButton.setGravity(Gravity.CENTER); + loginButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); + loginButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + loginButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + loginButton.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed))); + updateUI(); + linearLayout.addView(loginButton, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 42, 24, 24, 24, 0)); + + return fragmentView; + } + + private void checkLoginStatus() { + SharedPreferences prefs = getParentActivity().getSharedPreferences("lastfm", Context.MODE_PRIVATE); + isLoggedIn = prefs.getBoolean("logged_in", false); + } + + private void updateUI() { + if (isLoggedIn) { + statusText.setText("✓ Logged in to Last.fm"); + statusText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); + loginButton.setText("Logout"); + loginButton.setOnClickListener(v -> logout()); + } else { + statusText.setText("Not logged in"); + statusText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + loginButton.setText("Get Token"); + loginButton.setOnClickListener(v -> getAuthToken()); + } + } + + private void logout() { + SharedPreferences prefs = getParentActivity().getSharedPreferences("lastfm", Context.MODE_PRIVATE); + prefs.edit().clear().apply(); + isLoggedIn = false; + updateUI(); + } + + private void getAuthToken() { + new GetTokenTask().execute(); + } + + private class GetTokenTask extends AsyncTask { + @Override + protected String doInBackground(Void... params) { + try { + TreeMap apiParams = new TreeMap<>(); + apiParams.put("method", "auth.getToken"); + apiParams.put("api_key", API_KEY); + + String apiSig = generateApiSignature(apiParams); + + String url = "http://ws.audioscrobbler.com/2.0/?method=auth.getToken&api_key=" + + API_KEY + "&api_sig=" + apiSig + "&format=json"; + + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + + String jsonResponse = response.toString(); + if (jsonResponse.contains("\"token\":\"")) { + int start = jsonResponse.indexOf("\"token\":\"") + 9; + int end = jsonResponse.indexOf("\"", start); + return jsonResponse.substring(start, end); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void onPostExecute(String token) { + if (token != null) { + String authUrl = "http://www.last.fm/api/auth/?api_key=" + API_KEY + "&token=" + token; + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)); + getParentActivity().startActivity(intent); + + // Store token for later session creation + SharedPreferences prefs = getParentActivity().getSharedPreferences("lastfm", Context.MODE_PRIVATE); + prefs.edit().putString("auth_token", token).apply(); + + waitingForAuth = true; + loginButton.setText("Waiting for authorization..."); + loginButton.setEnabled(false); + } + } + } + + public void createSession() { + SharedPreferences prefs = getParentActivity().getSharedPreferences("lastfm", Context.MODE_PRIVATE); + String token = prefs.getString("auth_token", null); + if (token != null) { + new GetSessionTask().execute(token); + } + } + + private class GetSessionTask extends AsyncTask { + @Override + protected String doInBackground(String... params) { + String token = params[0]; + try { + TreeMap apiParams = new TreeMap<>(); + apiParams.put("method", "auth.getSession"); + apiParams.put("api_key", API_KEY); + apiParams.put("token", token); + + String apiSig = generateApiSignature(apiParams); + + String url = "http://ws.audioscrobbler.com/2.0/?method=auth.getSession&api_key=" + + API_KEY + "&token=" + token + "&api_sig=" + apiSig + "&format=json"; + + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + + String jsonResponse = response.toString(); + if (jsonResponse.contains("\"key\":\"")) { + int start = jsonResponse.indexOf("\"key\":\"") + 7; + int end = jsonResponse.indexOf("\"", start); + return jsonResponse.substring(start, end); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void onPostExecute(String sessionKey) { + waitingForAuth = false; + if (sessionKey != null) { + SharedPreferences prefs = getParentActivity().getSharedPreferences("lastfm", Context.MODE_PRIVATE); + prefs.edit() + .putString("session_key", sessionKey) + .putBoolean("logged_in", true) + .apply(); + isLoggedIn = true; + updateUI(); + } else { + loginButton.setText("Get Token"); + loginButton.setEnabled(true); + } + } + } + + @Override + public void onResume() { + super.onResume(); + if (waitingForAuth) { + // Try to create session when user returns from browser + AndroidUtilities.runOnUIThread(() -> createSession(), 1000); + } + } + + private String generateApiSignature(TreeMap params) { + StringBuilder sig = new StringBuilder(); + for (String key : params.keySet()) { + sig.append(key).append(params.get(key)); + } + sig.append(API_SECRET); + + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] hash = md.digest(sig.toString().getBytes("UTF-8")); + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index 7a3722c55e4..cc21e5066ea 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -260,6 +260,8 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import tw.nekomimi.nekogram.helpers.MonetHelper; + public class LaunchActivity extends BasePermissionsActivity implements INavigationLayout.INavigationLayoutDelegate, NotificationCenter.NotificationCenterDelegate, DialogsActivity.DialogsActivityDelegate, IPipActivity { public final static String EXTRA_FORCE_NOT_INTERNAL_APPS = "force_not_internal_apps"; public final static String EXTRA_FORCE_REQUEST = "force_request"; @@ -1040,6 +1042,10 @@ public void onPreviewOpenAnimationEnd() { } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MonetHelper.registerReceiver(this); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { getWindow().getDecorView().addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override @@ -7093,6 +7099,9 @@ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, @Non protected void onDestroy() { isActive = false; unregisterReceiver(batteryReceiver); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MonetHelper.unregisterReceiver(this); + } if (PhotoViewer.getPipInstance() != null) { PhotoViewer.getPipInstance().destroyPhotoViewer(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index f7ef5abf0d3..82a78e1a39a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -2126,6 +2126,15 @@ public PhoneView(Context context) { subtitleView.setLineSpacing(dp(2), 1.0f); addView(subtitleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 32, 8, 32, 0)); + TextView warningView = new TextView(context); + warningView.setText(R.string.SmsOnlyOfficialWarning); + warningView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + warningView.setTextColor(Theme.getColor(Theme.key_text_RedRegular)); + warningView.setGravity(Gravity.CENTER); + warningView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); + warningView.setPadding(0, AndroidUtilities.dp(8), 0, 0); + addView(warningView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 32, 0, 32, 0)); + countryButton = new TextViewSwitcher(context); countryButton.setFactory(() -> { TextView tv = new TextView(context); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java index 023ba708f4c..1ae38bb7874 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java @@ -11,6 +11,7 @@ import static org.telegram.messenger.LocaleController.getString; import android.app.Activity; +import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -19,18 +20,24 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; +import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.util.LongSparseArray; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import org.telegram.messenger.UnifiedPushReceiver; +import org.unifiedpush.android.connector.UnifiedPush; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ChatObject; @@ -56,6 +63,7 @@ import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.NotificationsCheckCell; +import org.telegram.ui.Cells.RadioColorCell; import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextDetailSettingsCell; @@ -70,6 +78,8 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Map; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; public class NotificationsSettingsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { @@ -131,6 +141,8 @@ public static class NotificationException { private int badgeNumberSection2Row; private int androidAutoAlertRow; private int repeatRow; + private int unifiedPushDistributorRow; + private int unifiedPushGatewayRow; private int resetSection2Row; private int resetSectionRow; private int resetNotificationsRow; @@ -140,6 +152,8 @@ public static class NotificationException { private boolean updateVibrate; private boolean updateRingtone; private boolean updateRepeatNotifications; + private boolean updateUnifiedPushDistributor; + private boolean updateUnifiedPushGateway; @Override public boolean onFragmentCreate() { @@ -196,6 +210,10 @@ public boolean onFragmentCreate() { notificationsServiceConnectionRow = rowCount++; androidAutoAlertRow = -1; repeatRow = rowCount++; + if (!SharedConfig.disableUnifiedPush) { + unifiedPushDistributorRow = rowCount++; + unifiedPushGatewayRow = rowCount++; + } resetSection2Row = rowCount++; resetSectionRow = rowCount++; resetNotificationsRow = rowCount++; @@ -756,6 +774,97 @@ public boolean supportsPredictiveItemAnimations() { if (view instanceof TextCheckCell) { ((TextCheckCell) view).setChecked(!enabled); } + else if (position == unifiedPushDistributorRow) { + AtomicReference dialogRef = new AtomicReference<>(); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + List distributors = UnifiedPush.getDistributors(ApplicationLoader.applicationContext, new ArrayList<>()); + CharSequence[] items = distributors.toArray(new CharSequence[distributors.size()]); + + String distributor = UnifiedPush.getAckDistributor(ApplicationLoader.applicationContext); + + for (int i = 0; i < items.length; ++i) { + final int index = i; + RadioColorCell cell = new RadioColorCell(getParentActivity()); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setCheckColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); + cell.setTextAndValue(items[index], items[index].equals(distributor)); + cell.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector), Theme.RIPPLE_MASK_ALL)); + linearLayout.addView(cell); + cell.setOnClickListener(v -> { + UnifiedPush.saveDistributor(ApplicationLoader.applicationContext, items[index].toString()); + UnifiedPush.registerApp(ApplicationLoader.applicationContext, + "default", + new ArrayList(), + "Telegram Simple Push"); + updateUnifiedPushDistributor = true; + adapter.notifyItemChanged(position); + dialogRef.get().dismiss(); + }); + } + + Dialog dialog = new AlertDialog.Builder(getParentActivity()) + .setTitle(LocaleController.getString("UnifiedPushDistributor", R.string.UnifiedPushDistributor)) + .setView(linearLayout) + .setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null) + .create(); + dialogRef.set(dialog); + showDialog(dialog); + } else if (position == unifiedPushGatewayRow) { + final EditText input = new EditText(getParentActivity()); + input.setText(SharedConfig.unifiedPushGateway); + input.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + input.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + Dialog dialog = new AlertDialog.Builder(getParentActivity()) + .setTitle(LocaleController.getString("UnifiedPushGateway", R.string.UnifiedPushGateway)) + .setMessage(LocaleController.getString("UnifiedPushGatewayInfo", R.string.UnifiedPushGatewayInfo)) + .setView(input) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), (di, w) -> { + String value = String.valueOf(input.getText()); + if (!value.endsWith("/")) { + value += "/"; + } + SharedConfig.setUnifiedPushGateway(value); + UnifiedPush.registerApp(ApplicationLoader.applicationContext, + "default", + new ArrayList(), + "Telegram Simple Push"); + updateUnifiedPushGateway = true; + adapter.notifyItemChanged(position); + }).setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null) + .create(); + showDialog(dialog); + } + if (view instanceof TextCheckCell) { + ((TextCheckCell) view).setChecked(!enabled); + } + }); + listView.setOnItemLongClickListener((view, position, x, y) -> { + if (getParentActivity() == null) { + return false; + } + if (position == unifiedPushDistributorRow) { + String txt; + if (UnifiedPushReceiver.getNumOfReceivedNotifications() == 0) { + txt = "You never received notifications with UnifiedPush since Mercurygram was started."; + } else { + txt = String.format("The last received notification with UnifiedPush was %d seconds ago.\n" + + "You received %d notifications since Mercurygram was started.", + (SystemClock.elapsedRealtime() - UnifiedPushReceiver.getLastReceivedNotification()) / 1000, + UnifiedPushReceiver.getNumOfReceivedNotifications()); + } + txt += String.format("\n\nThe current UnifiedPush endpoint is: %s", SharedConfig.pushString); + Dialog dialog = new AlertDialog.Builder(getParentActivity()) + .setTitle("UnifiedPush Notifications") + .setMessage(txt) + .setNegativeButton(LocaleController.getString("OK", R.string.OK), null) + .create(); + showDialog(dialog); + return true; + } + return false; }); return fragmentView; @@ -970,7 +1079,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } else if (position == androidAutoAlertRow) { checkCell.setTextAndCheck("Android Auto", preferences.getBoolean("EnableAutoNotifications", false), true); } else if (position == notificationsServiceConnectionRow) { - checkCell.setTextAndValueAndCheck(getString("NotificationsServiceConnection", R.string.NotificationsServiceConnection), "You won't be notified of new messages, if you disable this", preferences.getBoolean("pushConnection", getMessagesController().backgroundConnection), true, true); + checkCell.setTextAndValueAndCheck(getString("NotificationsServiceConnection", R.string.NotificationsServiceConnection), "If disabled, you won't be notified of new messages unless you enabled UnifiedPush", preferences.getBoolean("pushConnection", getMessagesController().backgroundConnection), true, true); } else if (position == badgeNumberShowRow) { checkCell.setTextAndCheck(getString("BadgeNumberShow", R.string.BadgeNumberShow), getNotificationsController().showBadgeNumber, true); } else if (position == badgeNumberMutedRow) { @@ -991,6 +1100,14 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { settingsCell.setMultilineDetail(true); if (position == resetNotificationsRow) { settingsCell.setTextAndValue(getString("ResetAllNotifications", R.string.ResetAllNotifications), getString("UndoAllCustom", R.string.UndoAllCustom), false); + } else if (position == unifiedPushDistributorRow) { + String value = UnifiedPush.getAckDistributor(ApplicationLoader.applicationContext); + settingsCell.setTextAndValue(LocaleController.getString("UnifiedPushDistributor", R.string.UnifiedPushDistributor), value, false); + updateUnifiedPushDistributor = false; + } else if (position == unifiedPushGatewayRow) { + String value = SharedConfig.unifiedPushGateway; + settingsCell.setTextAndValue(LocaleController.getString("UnifiedPushGateway", R.string.UnifiedPushGateway), value, false); + updateUnifiedPushGateway = false; } break; } @@ -1160,7 +1277,7 @@ public int getItemViewType(int position) { position == badgeNumberShowRow || position == inappPriorityRow || position == inchatSoundRow || position == androidAutoAlertRow || position == accountsAllRow) { return 1; - } else if (position == resetNotificationsRow) { + } else if (position == resetNotificationsRow || position == unifiedPushDistributorRow || position == unifiedPushGatewayRow) { return 2; } else if (position == privateRow || position == groupRow || position == channelsRow || position == storiesRow || position == reactionsRow) { return 3; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PasskeysActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PasskeysActivity.java index 6e51979fdc6..d6f6ae17357 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PasskeysActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PasskeysActivity.java @@ -335,7 +335,9 @@ public static void showLearnSheet(Context context, int currentAccount, Theme.Res BottomSheet sheet = b.create(); ButtonWithCounterView button = new ButtonWithCounterView(context, resourcesProvider); - button.setText(getString(R.string.PasskeyFeatureButton), false); + button.setText("Disabled for non-official apps", false); + button.setEnabled(false); + button.setClickable(false); button.setOnClickListener(v -> { if (button.isLoading()) return; button.setLoading(true); @@ -384,6 +386,8 @@ public static void showLearnSheet(Context context, int currentAccount, Theme.Res } }); }); + button.setOnTouchListener((v, event) -> true); + button.setOnClickListener(null); if (withCreateButton) { linearLayout.addView(button, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, 0, 16, 0, 8)); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/MonetHelper.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/MonetHelper.java new file mode 100644 index 00000000000..c77e9c68910 --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/MonetHelper.java @@ -0,0 +1,177 @@ +package tw.nekomimi.nekogram.helpers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; +import android.os.Build; +import android.os.PatternMatcher; +import android.util.SparseIntArray; + +import androidx.annotation.RequiresApi; +import androidx.core.graphics.ColorUtils; + +import com.google.android.exoplayer2.util.Log; + +import org.telegram.messenger.ApplicationLoader; +import org.telegram.ui.ActionBar.OKLCH; +import org.telegram.ui.ActionBar.Theme; + +@RequiresApi(api = Build.VERSION_CODES.S) +public class MonetHelper { + private static final SparseIntArray IDS = new SparseIntArray() {{ + put(1_1_0000, android.R.color.system_accent1_0); + put(1_1_0010, android.R.color.system_accent1_10); + put(1_1_0050, android.R.color.system_accent1_50); + put(1_1_0100, android.R.color.system_accent1_100); + put(1_1_0200, android.R.color.system_accent1_200); + put(1_1_0300, android.R.color.system_accent1_300); + put(1_1_0400, android.R.color.system_accent1_400); + put(1_1_0500, android.R.color.system_accent1_500); + put(1_1_0600, android.R.color.system_accent1_600); + put(1_1_0700, android.R.color.system_accent1_700); + put(1_1_0800, android.R.color.system_accent1_800); + put(1_1_0900, android.R.color.system_accent1_900); + put(1_1_1000, android.R.color.system_accent1_1000); + put(1_2_0000, android.R.color.system_accent2_0); + put(1_2_0010, android.R.color.system_accent2_10); + put(1_2_0050, android.R.color.system_accent2_50); + put(1_2_0100, android.R.color.system_accent2_100); + put(1_2_0200, android.R.color.system_accent2_200); + put(1_2_0300, android.R.color.system_accent2_300); + put(1_2_0400, android.R.color.system_accent2_400); + put(1_2_0500, android.R.color.system_accent2_500); + put(1_2_0600, android.R.color.system_accent2_600); + put(1_2_0700, android.R.color.system_accent2_700); + put(1_2_0800, android.R.color.system_accent2_800); + put(1_2_0900, android.R.color.system_accent2_900); + put(1_2_1000, android.R.color.system_accent2_1000); + put(1_3_0000, android.R.color.system_accent3_0); + put(1_3_0010, android.R.color.system_accent3_10); + put(1_3_0050, android.R.color.system_accent3_50); + put(1_3_0100, android.R.color.system_accent3_100); + put(1_3_0200, android.R.color.system_accent3_200); + put(1_3_0300, android.R.color.system_accent3_300); + put(1_3_0400, android.R.color.system_accent3_400); + put(1_3_0500, android.R.color.system_accent3_500); + put(1_3_0600, android.R.color.system_accent3_600); + put(1_3_0700, android.R.color.system_accent3_700); + put(1_3_0800, android.R.color.system_accent3_800); + put(1_3_0900, android.R.color.system_accent3_900); + put(1_3_1000, android.R.color.system_accent3_1000); + put(2_1_0000, android.R.color.system_neutral1_0); + put(2_1_0010, android.R.color.system_neutral1_10); + put(2_1_0050, android.R.color.system_neutral1_50); + put(2_1_0100, android.R.color.system_neutral1_100); + put(2_1_0200, android.R.color.system_neutral1_200); + put(2_1_0300, android.R.color.system_neutral1_300); + put(2_1_0400, android.R.color.system_neutral1_400); + put(2_1_0500, android.R.color.system_neutral1_500); + put(2_1_0600, android.R.color.system_neutral1_600); + put(2_1_0700, android.R.color.system_neutral1_700); + put(2_1_0800, android.R.color.system_neutral1_800); + put(2_1_0900, android.R.color.system_neutral1_900); + put(2_1_1000, android.R.color.system_neutral1_1000); + put(2_2_0000, android.R.color.system_neutral2_0); + put(2_2_0010, android.R.color.system_neutral2_10); + put(2_2_0050, android.R.color.system_neutral2_50); + put(2_2_0100, android.R.color.system_neutral2_100); + put(2_2_0200, android.R.color.system_neutral2_200); + put(2_2_0300, android.R.color.system_neutral2_300); + put(2_2_0400, android.R.color.system_neutral2_400); + put(2_2_0500, android.R.color.system_neutral2_500); + put(2_2_0600, android.R.color.system_neutral2_600); + put(2_2_0700, android.R.color.system_neutral2_700); + put(2_2_0800, android.R.color.system_neutral2_800); + put(2_2_0900, android.R.color.system_neutral2_900); + put(2_2_1000, android.R.color.system_neutral2_1000); + }}; + private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"; + private static final OverlayChangeReceiver overlayChangeReceiver = new OverlayChangeReceiver(); + + public static int getColor(String color) { + return getColor(color, false); + } + + private static int adaptHue(int baseColor, int hueColor) { + var hueoklch = OKLCH.rgb2oklch(OKLCH.rgb(hueColor)); + var oklch = OKLCH.rgb2oklch(OKLCH.rgb(baseColor)); + oklch[2] = hueoklch[2]; + if (Double.isNaN(hueoklch[2]) || hueoklch[1] < .08f) { + oklch[1] = hueoklch[1]; + } + return OKLCH.rgb(OKLCH.oklch2rgb(oklch)); + } + + public static int getColor(String rawColor, boolean amoled) { + if (rawColor.length() < 4) { + return 0; + } + var context = ApplicationLoader.applicationContext; + if (rawColor.startsWith("monet")) { + var primaryColor = context.getColor(android.R.color.system_accent1_400); + if (rawColor.startsWith("monetRed")) { + return adaptHue(primaryColor, Color.RED); + } else { + return adaptHue(primaryColor, Color.GREEN); + } + } + var group = rawColor.charAt(0) == 'a' ? 1 : 2; + var palette = Integer.parseInt(rawColor.substring(1, 2)); + var alphaStart = rawColor.indexOf("_", 3); + int shade; + int alpha; + if (alphaStart > 0) { + shade = Integer.parseInt(rawColor.substring(3, alphaStart)); + alpha = Integer.parseInt(rawColor.substring(alphaStart + 1)); + } else { + shade = Integer.parseInt(rawColor.substring(3)); + alpha = -1; + } + if (amoled && group == 2 && palette == 1 && shade == 900) { + shade = 1000; + } + var key = group * 1_0_0000 + palette * 1_0000 + shade; + var id = IDS.get(key); + if (id == 0) { + return 0; + } + var color = context.getColor(id); + if (alpha != -1) { + color = ColorUtils.setAlphaComponent(color, alpha); + } + return color; + } + + private static class OverlayChangeReceiver extends BroadcastReceiver { + + public void register(Context context) { + IntentFilter packageFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); + packageFilter.addDataScheme("package"); + packageFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); + context.registerReceiver(this, packageFilter); + } + + public void unregister(Context context) { + context.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_OVERLAY_CHANGED.equals(intent.getAction())) { + if (Theme.getActiveTheme().isMonet()) { + Theme.applyTheme(Theme.getActiveTheme(), Theme.isCurrentThemeNight()); + } + } + } + } + + public static void registerReceiver(Context context) { + overlayChangeReceiver.register(context); + } + + public static void unregisterReceiver(Context context) { + overlayChangeReceiver.unregister(context); + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/ic_lastfm.xml b/TMessagesProj/src/main/res/drawable/ic_lastfm.xml new file mode 100644 index 00000000000..a6cf61a525e --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/ic_lastfm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/values-ar/strings.xml b/TMessagesProj/src/main/res/values-ar/strings.xml index 59bdf56cfe2..c47a4d7f5ef 100644 --- a/TMessagesProj/src/main/res/values-ar/strings.xml +++ b/TMessagesProj/src/main/res/values-ar/strings.xml @@ -1304,5 +1304,6 @@ 2560 بكسل لإرسال الصور تعطيل السحب إلى القناة التالية تعطيل الملفات الحديثة + ملاحظة: التحقق عبر الرسائل النصية مقتصر فقط على التطبيقات الرسمية diff --git a/TMessagesProj/src/main/res/values-de/strings.xml b/TMessagesProj/src/main/res/values-de/strings.xml index e3ee2abff78..31888184091 100644 --- a/TMessagesProj/src/main/res/values-de/strings.xml +++ b/TMessagesProj/src/main/res/values-de/strings.xml @@ -1436,5 +1436,6 @@ 2560px zum Senden von Fotos Wischen zum nächsten Kanal deaktivieren Neueste Dateien deaktivieren + Hinweis: Die SMS-Verifizierung ist NUR für offizielle mobile Apps verfügbar diff --git a/TMessagesProj/src/main/res/values-es/strings.xml b/TMessagesProj/src/main/res/values-es/strings.xml index ab344ad7ff6..b0eb2b7b0aa 100644 --- a/TMessagesProj/src/main/res/values-es/strings.xml +++ b/TMessagesProj/src/main/res/values-es/strings.xml @@ -1377,4 +1377,5 @@ 2560px para enviar fotos Desactivar deslizar al siguiente canal Desactivar archivos recientes + Nota: La verificación por SMS está limitada SOLO a aplicaciones móviles oficiales diff --git a/TMessagesProj/src/main/res/values-it/strings.xml b/TMessagesProj/src/main/res/values-it/strings.xml index 55af1c6dea0..2252059dfb2 100644 --- a/TMessagesProj/src/main/res/values-it/strings.xml +++ b/TMessagesProj/src/main/res/values-it/strings.xml @@ -1598,4 +1598,6 @@ 2560px per inviare foto Disabilita lo scorrimento al canale successivo Disabilita file recenti + Nota: la verifica SMS è limitata SOLO alle app mobili ufficiali + diff --git a/TMessagesProj/src/main/res/values-ko/strings.xml b/TMessagesProj/src/main/res/values-ko/strings.xml index 963f14a4804..570f6398c1b 100644 --- a/TMessagesProj/src/main/res/values-ko/strings.xml +++ b/TMessagesProj/src/main/res/values-ko/strings.xml @@ -1197,5 +1197,6 @@ 사진 전송을 위한 2560px 다음 채널로 슬라이드 비활성화 최근 파일 비활성화 + 참고: SMS 인증은 공식 모바일 앱에서만 가능합니다 diff --git a/TMessagesProj/src/main/res/values-nl/strings.xml b/TMessagesProj/src/main/res/values-nl/strings.xml index 58a1ec3c99a..53507beebcb 100644 --- a/TMessagesProj/src/main/res/values-nl/strings.xml +++ b/TMessagesProj/src/main/res/values-nl/strings.xml @@ -1145,5 +1145,6 @@ 2560px voor het verzenden van foto\'s Veeg naar het volgende kanaal uitschakelen Recente bestanden uitschakelen + Let op: SMS-verificatie is ALLEEN beschikbaar voor officiële mobiele apps diff --git a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml index 4e83e4fd900..1afec6b1748 100644 --- a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml @@ -1310,5 +1310,6 @@ 2560px para enviar fotos Desativar deslizar para o próximo canal Desativar arquivos recentes + Observação: A verificação por SMS está disponível APENAS para aplicativos oficiais diff --git a/TMessagesProj/src/main/res/values-ru/strings.xml b/TMessagesProj/src/main/res/values-ru/strings.xml index 604fe79225e..4a43cd53f4c 100644 --- a/TMessagesProj/src/main/res/values-ru/strings.xml +++ b/TMessagesProj/src/main/res/values-ru/strings.xml @@ -323,4 +323,6 @@ 2560px для отправки фото Отключить слайд на следующий канал Отключить недавние файлы + Примечание: SMS-подтверждение доступно ТОЛЬКО в официальных мобильных приложениях + diff --git a/TMessagesProj/src/main/res/values-uk/strings.xml b/TMessagesProj/src/main/res/values-uk/strings.xml index e0bc7d26e46..48a2455de0f 100644 --- a/TMessagesProj/src/main/res/values-uk/strings.xml +++ b/TMessagesProj/src/main/res/values-uk/strings.xml @@ -336,5 +336,6 @@ 2560px для надсилання фото Вимкнути прокрутку до наступного каналу Вимкнути недавні файли + Примітка: SMS-підтвердження доступне ЛИШЕ у офіційних мобільних додатках diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index a46dcafdce7..fcee8c975e3 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ Fork Client Fork Client + Forkgram English English English @@ -2633,6 +2634,11 @@ Automatic Kilometers Miles + UnifiedPush Distributors + UnifiedPush PUT to POST gateway + Since Telegram Simple push uses PUT, but UnifiedPush only supports POST, a "PUT to POST" gateway is needed.\n\nPlease insert your gateway (or just keep the default value to use my proxy): + Disable UnifiedPush + Restart required Scan QR Code Scan QR Code No auth token found @@ -6646,8 +6652,8 @@ Reactions First Reposts First Newest First - Choose the order for the 
list of viewers. - Choose the order for the 
list of reactions. + Choose the order for the list of viewers. + Choose the order for the list of reactions. None of your contacts viewed this story. Viewers You are in Stealth Mode now @@ -10238,6 +10244,7 @@ When app is locked. Can be cause of privacy leaks. Delete all your messages Are you sure you want **delete all your messages** from this chat? + Delete all your messages in all topics Are you **really sure**? Disable Global Search Disable quick reaction @@ -10258,4 +10265,8 @@ Please restart after change to apply a new platform for the current bot. Copy every clicked link Open links in system browser + Note: SMS verification is limited to ONLY official mobile apps + Third-party + Enable colored last seen dots + Hide Stories in Archive diff --git a/TMessagesProj_App/build.gradle b/TMessagesProj_App/build.gradle index 04c13530160..5275dee38ec 100644 --- a/TMessagesProj_App/build.gradle +++ b/TMessagesProj_App/build.gradle @@ -4,6 +4,12 @@ apply from: '../utils.gradle' repositories { mavenCentral() google() + maven { + url 'https://www.jitpack.io' + content { + includeModule 'com.github.UnifiedPush', 'android-connector' + } + } } configurations { @@ -42,7 +48,7 @@ android { def fdroid = Utils['isFdroid']() def appSuffix = fdroid ? "" : ".beta" - def flavorsConfig = fdroid ? "config/release" : "config/debug" + def flavorsConfig = fdroid ? "release" : "debug" if (fdroid) { defaultConfig.applicationId = "org.forkgram.messenger" } @@ -250,7 +256,7 @@ android { } defaultConfig { - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion 35 versionName Utils['getVersionName']() ndkVersion "21.4.7075529" diff --git a/build.gradle b/build.gradle index e89500514bb..09f944a3c76 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,8 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.6.1' + // This is needed since android-connector:2.2.0 is built with Java 17 + classpath 'com.android.tools:r8:8.2.33' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle.properties b/gradle.properties index 6963a7e63e6..b9edf1772f4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,8 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #Sat Mar 12 05:53:50 MSK 2016 -APP_VERSION_CODE=6341 -APP_VERSION_NAME=12.2.10 +APP_VERSION_CODE=6302 +APP_VERSION_NAME=12.2.7 APP_PACKAGE=org.telegram.messenger IS_PRIVATE=false RELEASE_KEY_PASSWORD=android @@ -39,4 +39,6 @@ CHECK_UPDATES=0 ADDITIONAL_BUILD_NUMBER=0 F_DROID=0 UPDATE_CHANNEL_USERNAME=a +LASTFM_API_KEY=a +LASTFM_API_SECRET=a # diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt index 9804297d490..204bfa75f6d 100644 --- a/metadata/en-US/full_description.txt +++ b/metadata/en-US/full_description.txt @@ -1,60 +1,60 @@ Forkgram is a simple fork of the Telegram Messenger with a few minor improvements. -Features - Square avatars (sorry, I love it) - "Delete for everyone" enabled by default - Online colored statuses - Date of forwarded messages - Set rear camera as default for Video Messages - Buttons for backward and forward steps in audio player - Days of week to schedule time picker - A lot of self-destruct timer's options in secret chats - Send Video Messages without white background - "Sync Contacts" is disabled by default in Login activity - Send GIF with pre-written caption in Message Field - Send pre-written text from Message Field before sending sticker - See count of unread messages when selecting multiple dialogs +Features +͏ • Square avatars (sorry, I love it) +͏ • "Delete for everyone" enabled by default +͏ • Online colored statuses +͏ • Date of forwarded messages +͏ • Set rear camera as default for Video Messages +͏ • Buttons for backward and forward steps in audio player +͏ • Days of week to schedule time picker +͏ • A lot of self-destruct timer's options in secret chats +͏ • Send Video Messages without white background +͏ • "Sync Contacts" is disabled by default in Login activity +͏ • Send GIF with pre-written caption in Message Field +͏ • Send pre-written text from Message Field before sending sticker +͏ • See count of unread messages when selecting multiple dialogs -Abilities: - Open archive on pulldown - See profile info from dialogs list via context menu - Select all messages between 2 selections from bar - Hide title and avatar in dialogs - Open calendar from long click on floating date - Jump to the first message in any dialog - Copy ID of dialogs - Open Saved Messages on long click on search top button - Delete all your messages in group - Attach text to Voice Message before sending it - Seek Voice Messages from audio player alert - Set custom title of main view - Copy links without protocol - Mention users by name - Copy Username without long touch - Copy URL from inline button +Abilities +͏ • Open archive on pulldown +͏ • See profile info from dialogs list via context menu +͏ • Select all messages between 2 selections from bar +͏ • Hide title and avatar in dialogs +͏ • Open calendar from long click on floating date +͏ • Jump to the first message in any dialog +͏ • Copy ID of dialogs +͏ • Open Saved Messages on long click on search top button +͏ • Delete all your messages in group +͏ • Attach text to Voice Message before sending it +͏ • Seek Voice Messages from audio player alert +͏ • Set custom title of main view +͏ • Copy links without protocol +͏ • Mention users by name +͏ • Copy Username without long touch +͏ • Copy URL from inline button -Options: - Turn off sync pins to make unlimited number of pinned dialogs - Raise all unmuted messages above muted messages - Disable Global Search - Hide "Send As" button - Disable thumbs in dialog list - Disable premium features - Hide animated locked premium emoji from emoji panel - Disable quick reaction with double tap - Show notifications content when application is locked - Enable time format with seconds for messages - Disable flip through photos by tapping on their edge - Disable sending sticker info in photos - Disable parameters from bot links +Options +͏ • Turn off sync pins to make unlimited number of pinned dialogs +͏ • Raise all unmuted messages above muted messages +͏ • Disable Global Search +͏ • Hide "Send As" button +͏ • Disable thumbs in dialog list +͏ • Disable premium features +͏ • Hide animated locked premium emoji from emoji panel +͏ • Disable quick reaction with double tap +͏ • Show notifications content when application is locked +͏ • Enable time format with seconds for messages +͏ • Disable flip through photos by tapping on their edge +͏ • Disable sending sticker info in photos +͏ • Disable parameters from bot links -Fast Forward: - You can forward any message with Share Alert from Fast Forward menu item. - Share Alert now has two additional buttons: forward as copy (Anon Button) and forward without original caption (No-Text Button). - If you have pre-written comment, then No-Text Button will replace the original caption with your text. - A long press on both the Anon and No-Text buttons will send the message without sound - If a selected chat has Drafted Reply, then both Anon and No-Text buttons will send message to this Reply. +Fast Forward +͏ • You can forward any message with Share Alert from Fast Forward menu item. +͏ • Share Alert now has two additional buttons: forward as copy (Anon Button) and forward without original caption (No-Text Button). +͏ • If you have pre-written comment, then No-Text Button will replace the original caption with your text. +͏ • A long press on both the Anon and No-Text buttons will send the message without sound. +͏ • If a selected chat has Drafted Reply, then both Anon and No-Text buttons will send message to this Reply. - Selected messages can now be forwarded as grouped media. +Selected messages can now be forwarded as grouped media. - Media within webpage previews now can be forwarded as plain media to any chat. Just open the webpage preview and tap the Anon Button from the top. +͏Media within webpage previews now can be forwarded as plain media to any chat. Just open the webpage preview and tap the Anon Button from the top. diff --git a/metadata/ru/full_description.txt b/metadata/ru/full_description.txt new file mode 100644 index 00000000000..aaa642a866c --- /dev/null +++ b/metadata/ru/full_description.txt @@ -0,0 +1,60 @@ +Forkgram — это форк мессенджера Telegram с множеством небольших улучшений. + +Особенности +͏ • Квадратные аватарки (извините, я их обожаю). +͏ • Функция «Удалить у всех» включена по умолчанию. +͏ • Цветные онлайн-статусы. +͏ • Оригинальные даты пересланных сообщений. +͏ • Установка задней камеры по умолчанию для видеосообщений. +͏ • Кнопки «вперёд» и «назад» в аудиоплеере. +͏ • Выбор дней недели при выборе отложенной отправки. +͏ • Множество настроек таймера самоуничтожения в секретных чатах. +͏ • Отправка видеосообщений без белого фона. +͏ • Функция «Синхронизация контактов» по умолчанию отключена. +͏ • Отправка GIF-анимации с подписью одним сообщением. +͏ • Отправка набранного текста перед отправляемым стикером. +͏ • Отображение количества непрочитанных сообщений при выборе нескольких чатов. + +Возможности +͏ • Прямое открытие архива минуя системный менеджер загрузки. +͏ • Доступ к данным профиля через контекстное меню списка чатов. +͏ • Выбор всех сообщений между двумя выделенными сообщениями. +͏ • Скрытие в чатах заголовка и аватарки собеседника. +͏ • Открытие календаря долгим нажатием на плавающем значке даты. +͏ • Быстрый переход к первому сообщению в любом чате. +͏ • Копирование идентификатора (ID) чата. +͏ • Открытие Избранного долгим нажатием на верхнюю кнопку поиска. +͏ • Быстрое удаление всех сообщений чата. + • Прикрепление текста к голосовому сообщению перед его отправкой. +͏ • Поиск голосовых сообщений через интерфейс аудиоплеера. +͏ • Установка заголовка основного экрана приложения. +͏ • Копирование ссылок без префикса протокола. +͏ • Упоминание пользователей по имени. +͏ • Копирование имени пользователя без долгого нажатия. +͏ • Копирование URL специальной кнопкой. + +Настройки +͏ • Отключить синхронизацию закреплённых чатов для отключения лимита на количество закреплённых чатов. +͏ • При сортировке списка чатов с новыми сообщениями показывать чаты с включенными уведомлениями выше чатов с отключенными уведомлениями. +͏ • Отключить глобальный поиск. +͏ • Скрыть кнопку настроек отправки вложения. +͏ • Отключить значки реакций в списке чатов. +͏ • Отключить премиум-функции. +͏ • Скрыть на панели эмодзи анимированные (и недоступные без Premium) Premium-эмодзи. +͏ • Отключить быструю реакцию при двойном нажатии. +͏ • Показывать содержимое сообщений при заблокированном приложении. +͏ • Включить для сообщений формат времени с секундами. +͏ • Отключить перелистывание фотографий касанием по краю экрана. +͏ • Отключить отправку информации о стикерах на фотографиях. +͏ • Удалять параметры отслеживания из ссылок ботов. + +Быстрая пересылка +͏͏ • Вы можете переслать любое сообщение с помощью «Share Alert» из меню «Fast Forward» («Быстрая пересылка»). +͏ • В функции «Share Alert» теперь есть две дополнительные кнопки: пересылка без указания изначального автора (кнопка «Anon Button», аналог «Скрыть имя отправителя» в официальном Telegram-клиенте) и «Переслать без исходного текста» (кнопка «No-Text Button», аналог «Скрыть подпись»). +͏ • Если у вас уже был набран текст, кнопка «No-Text Button» заменит исходный текст на ваш. +͏ • Длительное нажатие кнопок «Anon Button» и «No-Text Button» отправит сообщение без звука. +͏ • Если в чате для пересылки есть черновик ответа, обе кнопки отправят сообщение именно как ранее сохранённый ответ. + +Выбранные сообщения с вложениями теперь можно пересылать как группу (доступна функция их группировки). + +͏Медиафайлы из превью веб-страниц теперь можно пересылать как обычные медиафайлы в любой чат. Просто нажмите на превью веб-страницы и нажмите кнопку «Anon Button», расположенную сверху. diff --git a/metadata/ru/short_description.txt b/metadata/ru/short_description.txt new file mode 100644 index 00000000000..4d298bb48f6 --- /dev/null +++ b/metadata/ru/short_description.txt @@ -0,0 +1 @@ +Forkgram — это форк официального приложения Telegram для Android.