diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java index ba4be1fed6..32e8b9c15b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java @@ -99,6 +99,7 @@ import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.ui.PinnedStickerHelper; +import xyz.nextalone.nagram.helper.ExternalStickerCacheHelper; @SuppressWarnings("unchecked") public class MediaDataController extends BaseController { @@ -1952,6 +1953,9 @@ public void addNewStickerSet(TLRPC.TL_messages_stickerSet set) { loadHash[type] = calcStickersHash(stickerSets[type]); getNotificationCenter().postNotificationName(NotificationCenter.stickersDidLoad, type, true); loadStickers(type, false, true); + + // Na: [ExternalStickerCache] cache sticker sets + ExternalStickerCacheHelper.cacheStickers(); } public void loadFeaturedStickers(boolean emoji, boolean cache, boolean force) { @@ -2722,6 +2726,9 @@ public void loadStickers(int type, boolean cache, boolean force, boolean schedul })); } } + + // Na: [ExternalStickerCache] cache sticker sets + ExternalStickerCacheHelper.cacheStickers(); } private void putStickersToCache(int type, ArrayList stickers, int date, long hash) { @@ -2757,6 +2764,9 @@ private void putStickersToCache(int type, ArrayList sets) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksAlert.java index d6e74e7862..456b0abe74 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksAlert.java @@ -64,6 +64,7 @@ import org.telegram.ui.Cells.StickerEmojiCell; import org.telegram.ui.Cells.StickerSetNameCell; import org.telegram.ui.ContentPreviewViewer; +import xyz.nextalone.nagram.helper.ExternalStickerCacheHelper; import java.util.ArrayList; import java.util.Arrays; @@ -1165,6 +1166,9 @@ private void updateStickerTabs() { stickersTab.onPageScrolled(lastPosition, lastPosition); } checkPanels(); + + // Na: [ExternalStickerCache] cache sticker sets + ExternalStickerCacheHelper.cacheStickers(); } private void checkPanels() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java index 90a45305ec..ca94da0889 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java @@ -109,6 +109,8 @@ import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.utils.ProxyUtil; +import xyz.nextalone.nagram.NaConfig; +import xyz.nextalone.nagram.helper.ExternalStickerCacheHelper; public class StickersAlert extends BottomSheet implements NotificationCenter.NotificationCenterDelegate { @@ -160,6 +162,8 @@ public interface StickersAlertCustomButtonDelegate { public boolean probablyEmojis; private int menu_archive = 4; + private final int menuRefreshExternalCache = 100; + private final int menuDeleteExternalCache = 101; private TLRPC.TL_messages_stickerSet stickerSet; private TLRPC.Document selectedSticker; @@ -865,6 +869,10 @@ public void requestLayout() { optionsButton.addSubItem(2, R.drawable.msg_link, LocaleController.getString("CopyLink", R.string.CopyLink)); optionsButton.addSubItem(3, R.drawable.msg_qrcode, LocaleController.getString("ShareQRCode", R.string.ShareQRCode)); optionsButton.addSubItem(menu_archive, R.drawable.msg_archive, LocaleController.getString("Archive", R.string.Archive)); + if (!NaConfig.INSTANCE.getExternalStickerCache().String().isBlank()) { + optionsButton.addSubItem(menuRefreshExternalCache, R.drawable.menu_views_reposts, LocaleController.getString(R.string.ExternalStickerCacheRefresh)); + optionsButton.addSubItem(menuDeleteExternalCache, R.drawable.msg_delete, LocaleController.getString(R.string.ExternalStickerCacheDelete)); + } optionsButton.setOnClickListener(v -> optionsButton.toggleSubMenu()); optionsButton.setDelegate(this::onSubItemClick); @@ -1092,6 +1100,12 @@ protected void onSend(LongSparseArray dids, int count, TLRPC.TL_fo } else if (id == menu_archive) { dismiss(); MediaDataController.getInstance(currentAccount).toggleStickerSet(parentActivity, stickerSet, 1, parentFragment, false, true); + } else if (id == menuRefreshExternalCache) { + // Na: [ExternalStickerCache] force refresh cache files + ExternalStickerCacheHelper.refreshCacheFiles(stickerSet); + } else if (id == menuDeleteExternalCache) { + // Na: [ExternalStickerCache] delete cache files + ExternalStickerCacheHelper.deleteCacheFiles(stickerSet); } } diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/config/cell/ConfigCellAutoTextCheck.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/config/cell/ConfigCellAutoTextCheck.java new file mode 100644 index 0000000000..884b8dfbf4 --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/config/cell/ConfigCellAutoTextCheck.java @@ -0,0 +1,78 @@ +package tw.nekomimi.nekogram.config.cell; + +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.LocaleController; +import org.telegram.ui.Cells.TextCheckCell; +import tw.nekomimi.nekogram.config.CellGroup; +import tw.nekomimi.nekogram.config.ConfigItem; + +import java.util.function.Consumer; + +public class ConfigCellAutoTextCheck extends AbstractConfigCell { + private final ConfigItem bindConfig; + private final Consumer onClick; + private final String title; + private String subtitle = null; + private final String subtitleFallback; + private boolean enabled = true; + public TextCheckCell cell; + + public ConfigCellAutoTextCheck(ConfigItem bindConfig, String subtitleFallback, Consumer onClick) { + this.bindConfig = bindConfig; + this.title = LocaleController.getString(bindConfig.getKey()); + this.subtitleFallback = subtitleFallback; + this.onClick = onClick; + } + + public int getType() { + return CellGroup.ITEM_TYPE_TEXT_CHECK; + } + + public String getKey() { + return bindConfig == null ? null : bindConfig.getKey(); + } + + public ConfigItem getBindConfig() { + return bindConfig; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (this.cell != null) + this.cell.setEnabled(this.enabled); + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + boolean checked = true; + if (subtitle == null || subtitle.isBlank()) { + subtitle = subtitleFallback; + checked = false; + } + if (cell != null) { + if (subtitle == null) { + cell.setTextAndCheck(title, checked, cellGroup.needSetDivider(this)); + } else { + cell.setTextAndValueAndCheck(title, subtitle, checked, true, cellGroup.needSetDivider(this)); + } + } + } + + public void onBindViewHolder(RecyclerView.ViewHolder holder) { + TextCheckCell cell = (TextCheckCell) holder.itemView; + this.cell = cell; + setSubtitle(subtitle); + cell.setEnabled(enabled, null); + } + + public void onClick() { + if (enabled) { + if (onClick != null) onClick.accept(cell.isChecked()); + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java index f5929236bd..e475e8357f 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoXSettingsActivity.java @@ -19,6 +19,7 @@ import tw.nekomimi.nekogram.config.CellGroup; import tw.nekomimi.nekogram.config.ConfigItem; import tw.nekomimi.nekogram.config.cell.AbstractConfigCell; +import tw.nekomimi.nekogram.config.cell.ConfigCellAutoTextCheck; import tw.nekomimi.nekogram.config.cell.ConfigCellCustom; import tw.nekomimi.nekogram.config.cell.ConfigCellSelectBox; import tw.nekomimi.nekogram.config.cell.ConfigCellTextCheck; @@ -88,6 +89,8 @@ protected String getRowKey(AbstractConfigCell row) { return ((ConfigCellTextInput) row).getKey(); } else if (row instanceof ConfigCellCustom) { return ((ConfigCellCustom) row).getKey(); + } else if (row instanceof ConfigCellAutoTextCheck) { + return ((ConfigCellAutoTextCheck) row).getKey(); } return null; } diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java index 4ad1273fc0..8fdafff6fa 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoExperimentalSettingsActivity.java @@ -28,6 +28,7 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.EmptyCell; @@ -60,6 +61,7 @@ import tw.nekomimi.nekogram.config.cell.AbstractConfigCell; import tw.nekomimi.nekogram.config.cell.*; import xyz.nextalone.nagram.NaConfig; +import xyz.nextalone.nagram.helper.ExternalStickerCacheHelper; @SuppressLint("RtlHardcoded") public class NekoExperimentalSettingsActivity extends BaseNekoXSettingsActivity { @@ -102,10 +104,33 @@ public class NekoExperimentalSettingsActivity extends BaseNekoXSettingsActivity private final AbstractConfigCell customArtworkApiRow = cellGroup.appendCell(new ConfigCellTextInput(null, NaConfig.INSTANCE.getCustomArtworkApi(), "", null)); private final AbstractConfigCell fakeHighPerformanceDeviceRow = cellGroup.appendCell(new ConfigCellTextCheck(NaConfig.INSTANCE.getFakeHighPerformanceDevice())); private final AbstractConfigCell disableEmojiDrawLimitRow = cellGroup.appendCell(new ConfigCellTextCheck(NaConfig.INSTANCE.getDisableEmojiDrawLimit())); + private final AbstractConfigCell externalStickerCacheRow = cellGroup.appendCell(new ConfigCellAutoTextCheck( + NaConfig.INSTANCE.getExternalStickerCache(), LocaleController.getString(R.string.ExternalStickerCacheHint), this::onExternalStickerCacheButtonClick)); private final AbstractConfigCell divider1 = cellGroup.appendCell(new ConfigCellDivider()); private UndoView tooltip; + private static final int INTENT_PICK_CUSTOM_EMOJI_PACK = 114; + private static final int INTENT_PICK_EXTERNAL_STICKER_DIRECTORY = 514; + + private void refreshExternalStickerStorageState() { + ConfigCellAutoTextCheck cell = (ConfigCellAutoTextCheck) externalStickerCacheRow; + Context context = ApplicationLoader.applicationContext; + ExternalStickerCacheHelper.checkUri(cell, context); + } + + private void onExternalStickerCacheButtonClick(boolean isChecked) { + if (isChecked) { + // clear config + ConfigCellAutoTextCheck cell = (ConfigCellAutoTextCheck) externalStickerCacheRow; + cell.setSubtitle(null); + NaConfig.INSTANCE.getExternalStickerCache().setConfigString(""); + } else { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + startActivityForResult(intent, INTENT_PICK_EXTERNAL_STICKER_DIRECTORY); + } + } + @Override public boolean onFragmentCreate() { super.onFragmentCreate(); @@ -133,6 +158,8 @@ public void onItemClick(int id) { } }); + refreshExternalStickerStorageState(); // Cell (externalStickerCacheRow): Refresh state + listAdapter = new ListAdapter(context); fragmentView = new FrameLayout(context); @@ -154,6 +181,8 @@ public void onItemClick(int id) { ((ConfigCellSelectBox) a).onClick(view); } else if (a instanceof ConfigCellTextInput) { ((ConfigCellTextInput) a).onClick(); + } else if (a instanceof ConfigCellAutoTextCheck) { + ((ConfigCellAutoTextCheck) a).onClick(); } else if (a instanceof ConfigCellTextDetail) { RecyclerListView.OnItemClickListener o = ((ConfigCellTextDetail) a).onItemClickListener; if (o != null) { @@ -251,7 +280,7 @@ public void onItemClick(int id) { intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/zip"); Activity act = getParentActivity(); - act.startActivityFromChild(act, intent, 114); + act.startActivityFromChild(act, intent, INTENT_PICK_CUSTOM_EMOJI_PACK); } }; @@ -266,7 +295,7 @@ public void onItemClick(int id) { @Override public void onActivityResultFragment(int requestCode, int resultCode, Intent data) { - if (requestCode == 114 && resultCode == Activity.RESULT_OK) { + if (requestCode == INTENT_PICK_CUSTOM_EMOJI_PACK && resultCode == Activity.RESULT_OK) { try { // copy emoji zip Uri uri = data.getData(); @@ -299,6 +328,15 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat } tooltip.showWithAction(0, UndoView.ACTION_NEED_RESATRT, null, null); // listAdapter.notifyItemChanged(cellGroup.rows.indexOf(useCustomEmojiRow)); + } else if (requestCode == INTENT_PICK_EXTERNAL_STICKER_DIRECTORY && resultCode == Activity.RESULT_OK) { + Uri uri = data.getData(); + // reserve permissions + int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + ApplicationLoader.applicationContext.getContentResolver().takePersistableUriPermission(uri, takeFlags); + // save config + NaConfig.INSTANCE.setExternalStickerCacheUri(uri); + refreshExternalStickerStorageState(); + tooltip.showWithAction(0, UndoView.ACTION_NEED_RESATRT, null, null); } } diff --git a/TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt b/TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt index eb1ed83f15..30b2accfaf 100644 --- a/TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt +++ b/TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt @@ -2,6 +2,7 @@ package xyz.nextalone.nagram import android.content.Context import android.content.SharedPreferences +import android.net.Uri import android.util.Base64 import org.telegram.messenger.ApplicationLoader import org.telegram.messenger.LocaleController @@ -478,6 +479,15 @@ object NaConfig { ConfigItem.configTypeBool, false ) + val externalStickerCache = + addConfig( + "ExternalStickerCache", + ConfigItem.configTypeString, + "" + ) + var externalStickerCacheUri: Uri? + get() = externalStickerCache.String().let { if (it.isBlank()) return null else return Uri.parse(it) } + set(value) = externalStickerCache.setConfigString(value.toString()) private fun addConfig( k: String, diff --git a/TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/helper/ExternalStickerCacheHelper.kt b/TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/helper/ExternalStickerCacheHelper.kt new file mode 100644 index 0000000000..1fe6f1ce00 --- /dev/null +++ b/TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/helper/ExternalStickerCacheHelper.kt @@ -0,0 +1,237 @@ +package xyz.nextalone.nagram.helper + +import android.content.Context +import android.util.Log +import androidx.documentfile.provider.DocumentFile +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.telegram.messenger.AndroidUtilities +import org.telegram.messenger.ApplicationLoader +import org.telegram.messenger.LocaleController +import org.telegram.messenger.MediaDataController +import org.telegram.messenger.R +import org.telegram.messenger.UserConfig +import org.telegram.tgnet.TLRPC.TL_messages_stickerSet +import tw.nekomimi.nekogram.config.cell.ConfigCellAutoTextCheck +import tw.nekomimi.nekogram.utils.AlertUtil +import xyz.nextalone.nagram.NaConfig +import java.io.File + +object ExternalStickerCacheHelper { + const val TAG = "ExternalStickerCache" + + private val cachePath = ApplicationLoader.applicationContext.getExternalFilesDir(null)!!.resolve("caches") + + @JvmStatic + fun checkUri(configCell: ConfigCellAutoTextCheck, context: Context) { + CoroutineScope(Dispatchers.IO).launch { + NaConfig.externalStickerCacheUri?.let { uri -> + AndroidUtilities.runOnUIThread { configCell.setSubtitle("Loading...") } + val dir = DocumentFile.fromTreeUri(context, uri) + var subtitle: String + if (dir == null) { + subtitle = "Error: failed to access document" + } else { + if (dir.isDirectory) { + val testFile = dir.findFile("test") ?: dir.createFile("text/plain", "test") + if (testFile == null) { + subtitle = "Error: cannot create file" + } else { + if (testFile.canRead() && testFile.canWrite()) { + subtitle = "Currently using: ${dir.name}" + } else { + subtitle = "Error: read/write is not supported" + } + if (!testFile.delete()) subtitle = "Error: cannot delete file" + } + } else { + subtitle = "Error: not a directory" + } + } + AndroidUtilities.runOnUIThread { configCell.setSubtitle(subtitle) } + } + } + } + + private var caching = false + private var cacheAgain = false + + @JvmStatic + fun cacheStickers() { + if (caching) { + cacheAgain = true + return + } + if (NaConfig.externalStickerCache.String().isEmpty()) return + CoroutineScope(Dispatchers.IO).launch { + caching = true + val stickerSets = MediaDataController.getInstance(UserConfig.selectedAccount).getStickerSets(MediaDataController.TYPE_IMAGE) + val context = ApplicationLoader.applicationContext + try { + val uri = NaConfig.externalStickerCacheUri ?: return@launch + val resolver = context.contentResolver + DocumentFile.fromTreeUri(context, uri)?.let { dir -> + logD("Caching ${stickerSets.size} sticker set(s)...") + if (dir.isDirectory) { + val stickerSetDirMap = dir.listFiles().run { + val map = mutableMapOf() + forEach { it.name?.let { name -> map[name] = it } } + map + } + stickerSets.forEach { stickerSetObject -> + val stickerSet = stickerSetObject.set + val stickers = stickerSetObject.documents + val idString = stickerSet.id.toString() + (stickerSetDirMap[idString] ?: dir.createDirectory(idString))?.let { stickerSetDir -> + if (stickerSetDir.isDirectory) { + val stickerFileMap = stickerSetDir.listFiles().run { + val map = mutableMapOf() + forEach { it.name?.let { name -> map[name] = it } } + map + } + for (sticker in stickers) { + val webp = "image/webp" + val webpExt = ".webp" + val localPath = "${sticker.dc_id}_${sticker.id}.webp" + val localPathLowQuality = "-${sticker.id}_1109.webp" + val stickerFile = File(cachePath, localPath) + val stickerFileLowQuality = File(cachePath, localPathLowQuality) + val destName = "${sticker.id}_high" + val destNameExt = destName + webpExt + val destNameNotFound = stickerFileMap[destNameExt] == null + val destNameLowQuality = "${sticker.id}_low" + val destNameLowQualityExt = destNameLowQuality + webpExt + + if (stickerFile.exists()) { + if (destNameNotFound) { + stickerSetDir.createFile(webp, destName)?.let { destFile -> + resolver.openOutputStream(destFile.uri)?.let { stickerFile.inputStream().copyTo(it) } + logV("Created file ${destFile.name}") + stickerFileMap[destNameLowQualityExt]?.let { + it.delete() + logV("Deleted low quality file") + } + } + } + } else if (stickerFileLowQuality.exists()) { + if (stickerFileMap[destNameLowQualityExt] == null && destNameNotFound) { + stickerSetDir.createFile(webp, destNameLowQuality)?.let { destFileLowQuality -> + resolver.openOutputStream(destFileLowQuality.uri)?.let { stickerFileLowQuality.inputStream().copyTo(it) } + logV("Created low quality file ${destFileLowQuality.name}") + } + } + } + } + } + } + } + } + } + } catch (e: Exception) { + logException(e, "caching stickers") + } + if (cacheAgain) { + delay(30000) + cacheAgain = false + cacheStickers() + } else { + caching = false + } + } + } + + @JvmStatic + fun refreshCacheFiles(set: TL_messages_stickerSet) { + CoroutineScope(Dispatchers.IO).launch { + val uri = NaConfig.externalStickerCacheUri ?: return@launch + val context = ApplicationLoader.applicationContext + try { + DocumentFile.fromTreeUri(context, uri)?.let { dir -> + val stickerSet = set.set + val idString = stickerSet.id.toString() + logD("Refreshing cache $idString...") + dir.findFile(idString)?.let { + it.delete() + logD("Deleting exist files...") + while (true) { + delay(500) + if (dir.findFile(idString) == null) break + } + } + dir.createDirectory(idString)?.let { stickerSetDir -> + val stickers = set.documents + val resolver = context.contentResolver + for (sticker in stickers) { + val localPath = "${sticker.dc_id}_${sticker.id}.webp" + val localPathLowQuality = "-${sticker.id}_1109.webp" + val stickerFile = File(cachePath, localPath) + val stickerFileLowQuality = File(cachePath, localPathLowQuality) + fun create(name: String, type: String) = stickerSetDir.createFile(type, name) + val webp = "image/webp" + val destName = "${sticker.id}_high" + val destNameLowQuality = "${sticker.id}_low" + + if (stickerFile.exists()) { + create(destName, webp)?.let { destFile -> + resolver.openOutputStream(destFile.uri)?.let { stickerFile.inputStream().copyTo(it) } + logV("Created file ${destFile.name}") + } + } else if (stickerFileLowQuality.exists()) { + create(destNameLowQuality, webp)?.let { destFileLowQuality -> + resolver.openOutputStream(destFileLowQuality.uri)?.let { stickerFileLowQuality.inputStream().copyTo(it) } + logV("Created low quality file ${destFileLowQuality.name}") + } + } + } + } + } + showToast(null) + } catch (e: Exception) { + logException(e, "refreshing specific cache") + } + } + } + + @JvmStatic + fun deleteCacheFiles(set: TL_messages_stickerSet) { + CoroutineScope(Dispatchers.IO).launch { + val uri = NaConfig.externalStickerCacheUri ?: return@launch + val context = ApplicationLoader.applicationContext + try { + DocumentFile.fromTreeUri(context, uri)?.let { dir -> + val stickerSet = set.set + val idString = stickerSet.id.toString() + dir.findFile(idString)?.delete() + } + showToast(null) + } catch (e: Exception) { + logException(e, "deleting specific cache") + } + } + } + + @JvmStatic + private fun showToast(msg: String?) { + var realMessage = msg + if (realMessage == null) { + realMessage = LocaleController.getString("Done", R.string.Done) + } + AndroidUtilities.runOnUIThread { + if (realMessage != null) { + AlertUtil.showToast(realMessage) + } + } + } + + private fun logException(e: Exception, s: String) { + val exception = e.javaClass.canonicalName + val message = e.message + val realMessage = "Exception while $s: $exception: $message" + Log.e(TAG, realMessage) + showToast(realMessage) + } + private fun logV(message: String) = Log.v(TAG, message) + private fun logD(message: String) = Log.d(TAG, message) +} diff --git a/TMessagesProj/src/main/res/values-zh-rCN/strings_na.xml b/TMessagesProj/src/main/res/values-zh-rCN/strings_na.xml index 90939482f0..17d67687f4 100644 --- a/TMessagesProj/src/main/res/values-zh-rCN/strings_na.xml +++ b/TMessagesProj/src/main/res/values-zh-rCN/strings_na.xml @@ -95,4 +95,8 @@ 禁用私聊的自定义背景 禁用频道的自定义背景 复制图片但是贴纸 + 在外部存储缓存贴纸文件 + 用于与其他应用共享贴纸包 (点击选择存储位置) + 刷新外部缓存 + 删除外部缓存 diff --git a/TMessagesProj/src/main/res/values/strings_na.xml b/TMessagesProj/src/main/res/values/strings_na.xml index bccc8a1f7e..0c15d27d5e 100644 --- a/TMessagesProj/src/main/res/values/strings_na.xml +++ b/TMessagesProj/src/main/res/values/strings_na.xml @@ -98,4 +98,8 @@ Disable user custom wallpaper Disable channel custom wallpaper Copy Photo As Sticker + External sticker cache + For sharing sticker packs with other apps. Click to pick storage location. + Refresh External Cache + Delete External Cache