diff --git a/app/build.gradle b/app/build.gradle index 4182bde7..77341663 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { defaultConfig { minSdk 21 targetSdk 32 - versionCode 449 - versionName "3.11.3" + versionCode 450 + versionName "3.12.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } flavorDimensions "default" diff --git a/app/src/main/assets/release_notes/notes.json b/app/src/main/assets/release_notes/notes.json index b72d5fac..a98bff24 100644 --- a/app/src/main/assets/release_notes/notes.json +++ b/app/src/main/assets/release_notes/notes.json @@ -1,4 +1,9 @@ [ + { + "version": "3.12.0", + "code": "450", + "note": "Added:\n- Full data import/export feature\n- Android 13 themed icon support\n\nFixed:\n- Fix a regression with filters\n- Fix dark solarized theme\n- Fix hide link previews for CW\n- Fix status bar color for all themes\n- Fix language in compose \"...\"\n- Fix add all home muted accounts from lists\n- Fix top notification badges" + }, { "version": "3.11.3", "code": "449", diff --git a/app/src/main/java/app/fedilab/android/activities/BaseActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseActivity.java index e7ba4af8..2f2b870f 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseActivity.java @@ -19,11 +19,8 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; -import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.view.Window; -import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -84,9 +81,6 @@ public class BaseActivity extends AppCompatActivity { break; case "BLACK": setTheme(R.style.BlackAppTheme); - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.BLACK); currentThemeId = R.style.BlackAppTheme; break; case "DRACULA": @@ -121,9 +115,6 @@ public class BaseActivity extends AppCompatActivity { case "BLACK": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); setTheme(R.style.BlackAppTheme); - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.BLACK); currentThemeId = R.style.BlackAppTheme; break; case "DRACULA": diff --git a/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java index 2776d2ec..e8f0111e 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseBarActivity.java @@ -19,11 +19,8 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; -import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.view.Window; -import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -76,9 +73,6 @@ public class BaseBarActivity extends AppCompatActivity { setTheme(R.style.SolarizedAppThemeBar); break; case "BLACK": - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.BLACK); setTheme(R.style.BlackAppThemeBar); break; case "DRACULA": @@ -108,9 +102,6 @@ public class BaseBarActivity extends AppCompatActivity { break; case "BLACK": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.BLACK); setTheme(R.style.BlackAppThemeBar); break; case "DRACULA": diff --git a/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java index 0f266864..77f2df73 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseTransparentActivity.java @@ -19,11 +19,8 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; -import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.view.Window; -import android.view.WindowManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -76,9 +73,6 @@ public class BaseTransparentActivity extends AppCompatActivity { setTheme(R.style.TransparentSolarized); break; case "BLACK": - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.BLACK); setTheme(R.style.TransparentBlack); break; case "DRACULA": @@ -108,9 +102,6 @@ public class BaseTransparentActivity extends AppCompatActivity { break; case "BLACK": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.BLACK); setTheme(R.style.TransparentBlack); break; case "DRACULA": diff --git a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java index 4b91601b..0262a836 100644 --- a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java @@ -54,9 +54,9 @@ public class LoginActivity extends BaseActivity { public static Account.API apiLogin; public static String currentInstanceLogin, client_idLogin, client_secretLogin, softwareLogin; - private final int PICK_IMPORT = 5557; public static boolean requestedAdmin; + @SuppressLint("ApplySharedPref") public void proceedLogin(Activity activity, Account account) { new Thread(() -> { @@ -154,6 +154,8 @@ public class LoginActivity extends BaseActivity { //The activity handles a redirect URI, it will extract token code and will proceed to authentication //That happens when the user wants to use an external browser manageItent(getIntent()); + + } @@ -176,7 +178,6 @@ public class LoginActivity extends BaseActivity { // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); - if (id == R.id.action_proxy) { Intent intent = new Intent(LoginActivity.this, ProxyActivity.class); startActivity(intent); @@ -188,20 +189,5 @@ public class LoginActivity extends BaseActivity { return super.onOptionsItemSelected(item); } - @Override - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { - if (data == null || data.getData() == null) { - Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); - return; - } - // String filename = Helper.getFilePathFromURI(LoginActivity.this, data.getData()); - // Sqlite.importDB(LoginActivity.this, filename); - } else { - Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); - } - } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java b/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java index 0d621931..1ab34d21 100644 --- a/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java @@ -150,9 +150,7 @@ public class MastodonListActivity extends BaseBarActivity implements MastodonLis timelinesVM.getAccountsInList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mastodonList.id, null, null, 0) .observe(MastodonListActivity.this, accounts -> { if (accounts != null && accounts.size() > 0) { - for (Account account : accounts) { - accountsVM.muteHome(MainActivity.currentAccount, account); - } + accountsVM.muteAccountsHome(MainActivity.currentAccount, accounts); } }); dialog.dismiss(); diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/MutedAccounts.java b/app/src/main/java/app/fedilab/android/client/entities/app/MutedAccounts.java index a1d3b219..520804e2 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/MutedAccounts.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/MutedAccounts.java @@ -68,6 +68,7 @@ public class MutedAccounts implements Serializable { try { return gson.toJson(accounts); } catch (Exception e) { + e.printStackTrace(); return null; } } diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 3b985591..fe70d4fe 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -312,7 +312,7 @@ public class Helper { public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE"; public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE"; public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY"; - + public static final String TEMP_EXPORT_DATA = "TEMP_EXPORT_DATA"; public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84; public static final int EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE = 85; @@ -1276,6 +1276,50 @@ public class Helper { }).start(); } + + public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) { + new Thread(() -> { + InputStream selectedFileInputStream; + File file = null; + try { + String uriFullPath = uri.getPath(); + String[] uriFullPathStr = uriFullPath.split(":"); + String fullPath = uriFullPath; + if (uriFullPathStr.length > 1) { + fullPath = uriFullPathStr[1]; + } + final String fileName = Helper.dateFileToString(context, new Date()) + ".zip"; + selectedFileInputStream = context.getContentResolver().openInputStream(uri); + if (selectedFileInputStream != null) { + final File certCacheDir = new File(context.getCacheDir(), TEMP_EXPORT_DATA); + boolean isCertCacheDirExists = certCacheDir.exists(); + if (!isCertCacheDirExists) { + isCertCacheDirExists = certCacheDir.mkdirs(); + } + if (isCertCacheDirExists) { + String filePath = certCacheDir.getAbsolutePath() + "/" + fileName; + file = new File(filePath); + OutputStream selectedFileOutPutStream = new FileOutputStream(filePath); + byte[] buffer = new byte[1024]; + int length; + while ((length = selectedFileInputStream.read(buffer)) > 0) { + selectedFileOutPutStream.write(buffer, 0, length); + } + selectedFileOutPutStream.flush(); + selectedFileOutPutStream.close(); + } + selectedFileInputStream.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + File finalFile = file; + Runnable myRunnable = () -> callBack.onFileCopied(finalFile); + mainHandler.post(myRunnable); + }).start(); + } + public static void createAttachmentFromPAth(String path, OnAttachmentCopied callBack) { new Thread(() -> { Attachment attachment = new Attachment(); @@ -1954,6 +1998,10 @@ public class Helper { void onAttachmentCopied(Attachment attachment); } + public interface OnFileCopied { + void onFileCopied(File file); + } + public static void addMutedAccount(app.fedilab.android.client.entities.api.Account target) { if (MainActivity.filteredAccounts == null) { MainActivity.filteredAccounts = new ArrayList<>(); diff --git a/app/src/main/java/app/fedilab/android/helper/SettingsStorage.java b/app/src/main/java/app/fedilab/android/helper/SettingsStorage.java deleted file mode 100644 index 86a74a23..00000000 --- a/app/src/main/java/app/fedilab/android/helper/SettingsStorage.java +++ /dev/null @@ -1,134 +0,0 @@ -package app.fedilab.android.helper; -/* Copyright 2022 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import static app.fedilab.android.BaseMainActivity.currentAccount; -import static app.fedilab.android.helper.LogoHelper.getMainLogo; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Environment; - -import androidx.preference.PreferenceManager; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Date; -import java.util.Map; - -import app.fedilab.android.R; - - -//From https://stackoverflow.com/a/10864463 - -public class SettingsStorage { - - - public static boolean saveSharedPreferencesToFile(Context context) { - boolean res = false; - ObjectOutputStream output = null; - String fileName = "Fedilab_settings_export_" + Helper.dateFileToString(context, new Date()) + ".txt"; - String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - String fullPath = filePath + "/" + fileName; - File dst = new File(fullPath); - try { - output = new ObjectOutputStream(new FileOutputStream(dst)); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); - output.writeObject(sharedpreferences.getAll()); - res = true; - String message = context.getString(R.string.data_export_settings_success); - Intent intentOpen = new Intent(); - intentOpen.setAction(android.content.Intent.ACTION_VIEW); - Uri uri = Uri.parse("file://" + fullPath); - intentOpen.setDataAndType(uri, "text/txt"); - String title = context.getString(R.string.data_export_settings); - Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), - getMainLogo(context)), Helper.NotifType.BACKUP, title, message); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if (output != null) { - output.flush(); - output.close(); - } - } catch (IOException ex) { - ex.printStackTrace(); - } - } - return res; - } - - @SuppressLint("ApplySharedPref") - @SuppressWarnings({"unchecked", "UnnecessaryUnboxing"}) - public static boolean loadSharedPreferencesFromFile(Context context, Uri srcUri) { - boolean res = false; - ObjectInputStream input = null; - try { - input = new ObjectInputStream(context.getContentResolver().openInputStream(srcUri)); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor prefEdit = sharedpreferences.edit(); - prefEdit.clear(); - Map entries = (Map) input.readObject(); - for (Map.Entry entry : entries.entrySet()) { - Object v = entry.getValue(); - String key = entry.getKey(); - //We skip some values - if (key.compareTo(Helper.PREF_USER_ID) == 0) { - continue; - } - if (key.compareTo(Helper.PREF_INSTANCE) == 0) { - continue; - } - if (key.compareTo(Helper.PREF_USER_INSTANCE) == 0) { - continue; - } - if (key.compareTo(Helper.PREF_USER_TOKEN) == 0) { - continue; - } - if (v instanceof Boolean) - prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); - else if (v instanceof Float) - prefEdit.putFloat(key, ((Float) v).floatValue()); - else if (v instanceof Integer) - prefEdit.putInt(key, ((Integer) v).intValue()); - else if (v instanceof Long) - prefEdit.putLong(key, ((Long) v).longValue()); - else if (v instanceof String) - prefEdit.putString(key, ((String) v)); - } - prefEdit.commit(); - res = true; - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } finally { - try { - if (input != null) { - input.close(); - } - } catch (IOException ex) { - ex.printStackTrace(); - } - } - return res; - } -} diff --git a/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java b/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java index 589528a8..4ad014c1 100644 --- a/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java @@ -137,8 +137,6 @@ public class TimelineHelper { if (m.find()) { status.filteredByApp = filter; continue; - } else { - status.filteredByApp = null; } if (status.spoiler_text != null) { String spoilerText; @@ -149,8 +147,6 @@ public class TimelineHelper { Matcher ms = p.matcher(spoilerText); if (ms.find()) { status.filteredByApp = filter; - } else { - status.filteredByApp = null; } } } @@ -160,9 +156,6 @@ public class TimelineHelper { if (filterTimeLineType == Timeline.TimeLineEnum.HOME) { if (filteredAccounts != null && filteredAccounts.size() > 0) { for (Status status : statuses) { - if (status.filteredByApp != null) { - continue; - } for (Account account : filteredAccounts) { if (account.acct.equals(status.account.acct) || (status.reblog != null && account.acct.equals(status.reblog.account.acct))) { Filter filterCustom = new Filter(); diff --git a/app/src/main/java/app/fedilab/android/helper/ZipHelper.java b/app/src/main/java/app/fedilab/android/helper/ZipHelper.java new file mode 100644 index 00000000..e9873b94 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/ZipHelper.java @@ -0,0 +1,294 @@ +package app.fedilab.android.helper; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import static app.fedilab.android.BaseMainActivity.currentAccount; +import static app.fedilab.android.helper.LogoHelper.getMainLogo; +import static app.fedilab.android.sqlite.Sqlite.DB_NAME; +import static app.fedilab.android.sqlite.Sqlite.db; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.widget.Toast; + +import androidx.preference.PreferenceManager; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.channels.FileChannel; +import java.util.Date; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import app.fedilab.android.R; +import es.dmoral.toasty.Toasty; + + +public class ZipHelper { + + final static int BUFFER_SIZE = 2048; + + public static void exportData(Context context) throws IOException { + String suffix = Helper.dateFileToString(context, new Date()); + String fileName = "Fedilab_data_export_" + suffix + ".zip"; + String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); + String zipFile = filePath + "/" + fileName; + BufferedInputStream origin; + try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) { + byte[] data = new byte[BUFFER_SIZE]; + String settingsPath = storeSettings(context, suffix); + if (settingsPath != null) { + FileInputStream fi = new FileInputStream(settingsPath); + origin = new BufferedInputStream(fi, BUFFER_SIZE); + try { + ZipEntry entry = new ZipEntry(settingsPath.substring(settingsPath.lastIndexOf("/") + 1)); + out.putNextEntry(entry); + int count; + while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { + out.write(data, 0, count); + } + } finally { + origin.close(); + } + //noinspection ResultOfMethodCallIgnored + new File(settingsPath).delete(); + } else { + Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); + return; + } + String dbPath = exportDB(context, suffix); + if (dbPath != null) { + FileInputStream fi = new FileInputStream(dbPath); + origin = new BufferedInputStream(fi, BUFFER_SIZE); + try { + ZipEntry entry = new ZipEntry(dbPath.substring(dbPath.lastIndexOf("/") + 1)); + out.putNextEntry(entry); + int count; + while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { + out.write(data, 0, count); + } + } finally { + origin.close(); + } + //noinspection ResultOfMethodCallIgnored + new File(dbPath).delete(); + } else { + Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); + return; + } + String message = context.getString(R.string.data_export_settings_success); + Intent intentOpen = new Intent(); + intentOpen.setAction(android.content.Intent.ACTION_VIEW); + Uri uri = Uri.parse("file://" + zipFile); + intentOpen.setDataAndType(uri, "application/zip"); + String title = context.getString(R.string.data_export_settings); + Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), + getMainLogo(context)), Helper.NotifType.BACKUP, title, message); + } + } + + + @SuppressLint("UnspecifiedImmutableFlag") + public static void importData(Context context, File file) { + new Thread(() -> { + try { + int size; + byte[] buffer = new byte[BUFFER_SIZE]; + + String uriFullPath = file.getAbsolutePath(); + String[] uriFullPathStr = uriFullPath.split(":"); + String fullPath = uriFullPath; + if (uriFullPathStr.length > 1) { + fullPath = uriFullPathStr[1]; + } + fullPath = fullPath.replace(".zip", ""); + File f = new File(fullPath); + if (!f.isDirectory()) { + //noinspection ResultOfMethodCallIgnored + f.mkdirs(); + } + boolean successful = true; + try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fullPath + ".zip"))) { + ZipEntry ze; + while ((ze = zin.getNextEntry()) != null) { + if (!successful) { + break; + } + String path = fullPath + ze.getName(); + File unzipFile = new File(path); + FileOutputStream out = new FileOutputStream(unzipFile, false); + BufferedOutputStream fout = new BufferedOutputStream(out, BUFFER_SIZE); + try { + while ((size = zin.read(buffer, 0, BUFFER_SIZE)) != -1) { + fout.write(buffer, 0, size); + } + + zin.closeEntry(); + } finally { + fout.flush(); + fout.close(); + } + if (ze.getName().contains("settings")) { + successful = restoreSettings(context, Uri.fromFile(new File(path))); + } else if (ze.getName().contains("database")) { + successful = importDB(context, path); + } else { + break; + } + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + boolean finalSuccessful = successful; + Runnable myRunnable = () -> { + if (finalSuccessful) { + Helper.restart(context); + } else { + Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + } + }; + mainHandler.post(myRunnable); + + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + + } + + private static String storeSettings(Context context, String suffix) { + boolean res = false; + ObjectOutputStream output = null; + String fileName = "Fedilab_settings_export_" + suffix + ".fedilab"; + String filePath = context.getCacheDir().getAbsolutePath(); + String fullPath = filePath + "/" + fileName; + File dst = new File(fullPath); + try { + output = new ObjectOutputStream(new FileOutputStream(dst)); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); + output.writeObject(sharedpreferences.getAll()); + res = true; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (output != null) { + output.flush(); + output.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + return res ? fullPath : null; + } + + @SuppressLint("ApplySharedPref") + @SuppressWarnings("UnnecessaryUnboxing") + private static boolean restoreSettings(Context context, Uri srcUri) { + boolean res = false; + ObjectInputStream input = null; + try { + input = new ObjectInputStream(context.getContentResolver().openInputStream(srcUri)); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor prefEdit = sharedpreferences.edit(); + prefEdit.clear(); + //noinspection unchecked + Map entries = (Map) input.readObject(); + for (Map.Entry entry : entries.entrySet()) { + Object v = entry.getValue(); + String key = entry.getKey(); + if (v instanceof Boolean) + prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); + else if (v instanceof Float) + prefEdit.putFloat(key, ((Float) v).floatValue()); + else if (v instanceof Integer) + prefEdit.putInt(key, ((Integer) v).intValue()); + else if (v instanceof Long) + prefEdit.putLong(key, ((Long) v).longValue()); + else if (v instanceof String) + prefEdit.putString(key, ((String) v)); + } + + prefEdit.commit(); + res = true; + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } finally { + try { + if (input != null) { + input.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + return res; + } + + + private static String exportDB(Context context, String suffix) { + try { + String fileName = "Fedilab_database_export_" + suffix + ".fedilab"; + String filePath = context.getCacheDir().getAbsolutePath(); + String fullPath = filePath + "/" + fileName; + File dbSource = context.getDatabasePath(DB_NAME); + File dbDest = new File(fullPath); + FileChannel src = new FileInputStream(dbSource).getChannel(); + FileChannel dst = new FileOutputStream(dbDest).getChannel(); + dst.transferFrom(src, 0, src.size()); + src.close(); + dst.close(); + return fullPath; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static boolean importDB(Context context, String backupDBPath) { + try { + if (db != null) { + db.close(); + } + File dbDest = context.getDatabasePath(DB_NAME); + File dbSource = new File(backupDBPath); + FileChannel src = new FileInputStream(dbSource).getChannel(); + FileChannel dst = new FileOutputStream(dbDest).getChannel(); + dst.transferFrom(src, 0, src.size()); + src.close(); + dst.close(); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index 7c079c84..82df20bf 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -1129,10 +1129,16 @@ public class StatusAdapter extends RecyclerView.Adapter holder.binding.mediaContainer.setVisibility(View.GONE); } else { holder.binding.statusContent.setVisibility(View.VISIBLE); + if (statusToDeal.card != null && (display_card || statusToDeal.isFocused)) { + holder.binding.card.setVisibility(View.VISIBLE); + } else { + holder.binding.card.setVisibility(View.GONE); + } } } else { holder.binding.statusContent.setVisibility(View.GONE); holder.binding.mediaContainer.setVisibility(View.GONE); + holder.binding.card.setVisibility(View.GONE); } LayoutInflater inflater = ((Activity) context).getLayoutInflater(); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java index d8eadd7f..f4b41ef9 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java @@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.login; * see . */ +import static android.app.Activity.RESULT_OK; import static app.fedilab.android.activities.LoginActivity.apiLogin; import static app.fedilab.android.activities.LoginActivity.client_idLogin; import static app.fedilab.android.activities.LoginActivity.client_secretLogin; @@ -22,8 +23,9 @@ import static app.fedilab.android.activities.LoginActivity.currentInstanceLogin; import static app.fedilab.android.activities.LoginActivity.requestedAdmin; import static app.fedilab.android.activities.LoginActivity.softwareLogin; +import android.Manifest; import android.content.Intent; -import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.text.Editable; @@ -36,11 +38,13 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.widget.PopupMenu; +import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; @@ -55,6 +59,7 @@ import app.fedilab.android.client.entities.app.InstanceSocial; import app.fedilab.android.databinding.FragmentLoginMainBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; +import app.fedilab.android.helper.ZipHelper; import app.fedilab.android.viewmodel.mastodon.AppsVM; import app.fedilab.android.viewmodel.mastodon.InstanceSocialVM; import app.fedilab.android.viewmodel.mastodon.NodeInfoVM; @@ -65,7 +70,9 @@ public class FragmentLoginMain extends Fragment { private FragmentLoginMainBinding binding; private boolean searchInstanceRunning = false; private String oldSearch; - + private static final int REQUEST_CODE = 5412; + private final int PICK_IMPORT = 5557; + private ActivityResultLauncher permissionLauncher; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -76,6 +83,24 @@ public class FragmentLoginMain extends Fragment { InstanceSocialVM instanceSocialVM = new ViewModelProvider(FragmentLoginMain.this).get(InstanceSocialVM.class); binding.menuIcon.setOnClickListener(this::showMenu); binding.loginInstance.setOnItemClickListener((parent, view, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); + + permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); + openFileIntent.setType("application/zip"); + String[] mimeTypes = new String[]{"application/zip"}; + openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + //noinspection deprecation + startActivityForResult( + Intent.createChooser( + openFileIntent, + getString(R.string.load_settings)), PICK_IMPORT); + } else { + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); + } + }); + binding.loginInstance.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -184,7 +209,6 @@ public class FragmentLoginMain extends Fragment { MenuInflater menuInflater = popupMenu.getMenuInflater(); menuInflater.inflate(R.menu.main_login, popupMenu.getMenu()); MenuItem adminTabItem = popupMenu.getMenu().findItem(R.id.action_request_admin); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); adminTabItem.setChecked(requestedAdmin); popupMenu.setOnMenuItemClickListener(item -> { int itemId = item.getItemId(); @@ -208,6 +232,8 @@ public class FragmentLoginMain extends Fragment { return false; } }); + } else if (itemId == R.id.action_import_data) { + permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); } return false; }); @@ -258,4 +284,44 @@ public class FragmentLoginMain extends Fragment { }); } + + + @SuppressWarnings("deprecation") + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); + return; + } + Helper.createFileFromUri(requireActivity(), data.getData(), file -> ZipHelper.importData(requireActivity(), file)); + } else { + Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); + } + } + + + @SuppressWarnings("deprecation") + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CODE) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); + openFileIntent.setType("application/zip"); + String[] mimeTypes = new String[]{"application/zip"}; + openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + startActivityForResult( + Intent.createChooser( + openFileIntent, + getString(R.string.load_settings)), PICK_IMPORT); + } else { + Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show(); + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java b/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java index b8be1e3c..36d5b60e 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java @@ -19,6 +19,8 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; +import android.webkit.URLUtil; +import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -30,8 +32,11 @@ import androidx.navigation.Navigation; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import java.io.IOException; + import app.fedilab.android.R; -import app.fedilab.android.helper.SettingsStorage; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.ZipHelper; import es.dmoral.toasty.Toasty; public class FragmentSettingsCategories extends PreferenceFragmentCompat { @@ -117,7 +122,11 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat { } ActivityResultLauncher permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { - SettingsStorage.saveSharedPreferencesToFile(requireActivity()); + try { + ZipHelper.exportData(requireActivity()); + } catch (IOException e) { + e.printStackTrace(); + } } else { ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } @@ -152,12 +161,18 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat { @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (resultCode == Activity.RESULT_OK && requestCode == PICKUP_FILE) { - boolean result = data != null && SettingsStorage.loadSharedPreferencesFromFile(requireActivity(), data.getData()); - if (result) { - Toasty.success(requireActivity(), getString(R.string.data_import_settings_success), Toasty.LENGTH_LONG).show(); - } else { - Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + if (data == null || data.getData() == null) { + Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); + return; } + String uriFullPath = data.getData().getPath(); + String[] uriFullPathStr = uriFullPath.split(":"); + String fullPath = uriFullPath; + if (uriFullPathStr.length > 1) { + fullPath = uriFullPathStr[1]; + } + final String fileName = URLUtil.guessFileName(fullPath, null, null); + Helper.createFileFromUri(requireActivity(), data.getData(), file -> ZipHelper.importData(requireActivity(), file)); } } @@ -167,7 +182,11 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat { if (requestCode == REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - SettingsStorage.saveSharedPreferencesToFile(requireActivity()); + try { + ZipHelper.exportData(requireActivity()); + } catch (IOException e) { + e.printStackTrace(); + } } else { Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java index b8032f6a..cd55af29 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java @@ -813,7 +813,6 @@ public class AccountsVM extends AndroidViewModel { public LiveData muteHome(@NonNull BaseAccount forAccount, @NonNull Account target) { accountMutableLiveData = new MutableLiveData<>(); new Thread(() -> { - try { new MutedAccounts(getApplication().getApplicationContext()).muteAccount(forAccount, target); addMutedAccount(target); @@ -828,6 +827,32 @@ public class AccountsVM extends AndroidViewModel { return accountMutableLiveData; } + + /** + * Mute the given account in db + * + * @return {@link LiveData} containing the {@link Account} to the given account + */ + public LiveData> muteAccountsHome(@NonNull BaseAccount forAccount, @NonNull List targets) { + accountListMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + try { + for (Account target : targets) { + new MutedAccounts(getApplication().getApplicationContext()).muteAccount(forAccount, target); + sendAction(getApplication().getApplicationContext(), Helper.ARG_STATUS_ACCOUNT_ID_DELETED, null, target.id); + addMutedAccount(target); + } + } catch (DBException e) { + e.printStackTrace(); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> accountListMutableLiveData.setValue(targets); + mainHandler.post(myRunnable); + }).start(); + return accountListMutableLiveData; + } + + /** * Unmute the given account in db * diff --git a/app/src/main/res/drawable/shape_counter.xml b/app/src/main/res/drawable/shape_counter.xml index f45d2e75..c4cb4a23 100644 --- a/app/src/main/res/drawable/shape_counter.xml +++ b/app/src/main/res/drawable/shape_counter.xml @@ -1,7 +1,7 @@ - + diff --git a/app/src/main/res/layout/drawer_status_compose.xml b/app/src/main/res/layout/drawer_status_compose.xml index fab930de..1be888c3 100644 --- a/app/src/main/res/layout/drawer_status_compose.xml +++ b/app/src/main/res/layout/drawer_status_compose.xml @@ -190,7 +190,7 @@ \ No newline at end of file diff --git a/app/src/main/res/menu/main_login.xml b/app/src/main/res/menu/main_login.xml index 654c95e5..cbfb46fe 100644 --- a/app/src/main/res/menu/main_login.xml +++ b/app/src/main/res/menu/main_login.xml @@ -21,15 +21,17 @@ android:checkable="true" android:title="@string/admin_scope" app:actionViewClass="android.widget.CheckBox" /> + + + + + --> diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index ac94b34f..0648fb47 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom.xml index 5e2b456a..1f06a6a3 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom_round.xml index 5e2b456a..1f06a6a3 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_atom_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles.xml index 3b0f3d2b..6a103073 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles_round.xml index 3b0f3d2b..6a103073 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_bubbles_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash.xml index f9b24a1a..5dbe8bd2 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash_round.xml index f9b24a1a..5dbe8bd2 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_crash_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse.xml index 2b9a0f67..8b419637 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse_round.xml index 2b9a0f67..8b419637 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_fediverse_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero.xml index b0767e14..949c3282 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero_round.xml index b0767e14..949c3282 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hero_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab.xml index 0c1231b9..8ab24687 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab_round.xml index 0c1231b9..8ab24687 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mastalab_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index ac94b34f..0648fb47 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index c104834e..64c9420c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -160,9 +160,9 @@ Plánované datum musí být vyšší než aktuální hodina! Časový interval pro ztlumení musí být delší než jedna minuta. - %1$s byl ztlumen až do %2$s. + %1$s byl(a) ztlumen(a) až do %2$s. \n Ztlumení účtu můžete zrušit z jeho/její profilové stránky. - %1$s je ztlumen do %2$s. + %1$s je ztlumen(a) do %2$s. \n Klikněte zde pro zrušení ztlumení. Žádné upozornění k zobrazení @@ -299,10 +299,10 @@ Filtrované zprávy zmizí nezvratně i v případě, že je filtr později odstraněn V případě, že klíčové slovo nebo fráze je pouze alfanumerické, filtr se uplatní, pouze pokud odpovídá celému slovu Celé slovo - Kontext filtru + Kontexty filtru Jeden nebo několik kontextů pro aplikaci filtru Vyprší po - Vymazat filtr? + Smazat filtr\? Aktualizovat filtr Žádný seznam dosud nebyl vytvořen. Můžete vytvořit nový seznam klepnutím na tlačítko \"+\". Automaticky zobrazovat skrytá média @@ -387,7 +387,7 @@ volání http je blokováno aplikací Seznam blokovaných volání Odeslat - Filtrovat časovou osu s hashtagy + Filtrovat časovou osu s tagy Žádné tagy Při sdílení URL připojit obrázek @@ -926,4 +926,16 @@ Připnout tag Nesledovat tag Odepnout tag + Opravdu chcete ztlumit tag %1$s\? + Ztlumit tag + Zrušit ztlumení tagu + Odmítat média + Ztišení učiní příspěvky účtu neviditelné pro kohokoliv, kdo ho nesleduje. Pozastavení odstraní všechen obsah, média a profilová data účtu. Použijte Žádný, pokud chcete jen odmítat soubory médií. + Zrušeno ztišení účtu + Ztlumit všechny + Domácí ztlumení uživatelé + Účet ztišen + Ztlumit pro domovskou časovou osu + Zrušit ztlumení pro domovskou časovou osu + Všechny účtu budou na domovské časové ose ztlumeny. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5a56cb95..9b11eedb 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -440,7 +440,7 @@ J\'accepte les %1$s et les %2$s règles du serveur conditions de service - S’inscrire + S\'inscrire Cette instance fonctionne avec des invitations. Votre compte devra être approuvé manuellement par un·e administrateur·rice pour qu\'il devienne utilisable. Les mots de passe ne sont pas identiques ! L\'adresse ne semble pas être valide ! @@ -541,7 +541,7 @@ Faire une action Traduction Couleur du texte - Changer la couleur du texte dans les publications/messages + Changer la couleur du texte dans les messages Utiliser un thème personnalisé Thème Le thème a été exporté @@ -894,5 +894,43 @@ Pas intéressé Avertir des mises à jour Supprimer le fil chronologique - Signé + Inscrit·e + Thème sombre par défaut + L\'app n\'a pas trouvé le message distant. + Ordonner les listes + Sévérité + Le blocage de domaine n\'empêchera pas la création d\'entrées de compte dans la base de données, mais appliquera rétroactivement et automatiquement des méthodes de modération spécifiques à ces comptes. + Rejeter les fichiers médias + Personnaliser les couleurs + Choisissez un mode pour le thème + Permet de définir vos propres couleurs pour les thèmes. + Couleur dynamique + Aligne le ton avec les couleurs de votre fond d\'écran. + Permet de personnaliser certains éléments des messages pour le thème clair. + Afficher la conversation distante + Sombre - Couleurs personnalisées + Veuillez réessayer plus tard. + Rétablir l\'étiquette + Surélever les cartes + Personnaliser le thème clair + Personnaliser le thème sombre + Permet de personnaliser certains éléments des messages pour le thème sombre. + Définir les couleurs personnalisés + Clair - Couleurs personnalisées + La conversation a débuté sur votre instance ! + Silencer l\'étiquette + Épingler l\'étiquette + Désépingler l\'étiquette + Ne plus suivre l\'étiquette + Silencer sur l\'accueil + Rétablir sur l\'accueil + Les silencer tous + Si actif, les éléments des timelines auront une ombre et seront surélevés. + Tous les comptes seront silencés sur la timeline d\'accueil. + Toujours afficher le bouton de traduction + Thème clair par défaut + Silencier rendra les publications du compte invisibles à quiconque n\'étant pas abonné. Suspendre supprimera tous les contenus du compte, médias, et données du profil. Utilisez Aucun si vous souhaitez rejeter uniquement les fichiers médias. + utilisateur·ices silencé·es sur l\'accueil + Soumettre un rapport + Masquer partiellement le nom de domaine dans la liste si l\'option de publication restreinte de la liste de domaine est activée \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 7146b374..c514e968 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -865,7 +865,7 @@ Il report é stato inviato! \"Mastodon non é un singolo sito come Twitter o Facebook, é una rete di migliaia di communità gestite da organizzazioni e individui differenti che forniscono un\'esperienza social integrata.\" Segna tutte le notifiche come lette - Sei sicuro di voler eliminare tutte le notifiche\? L\'azione non puó essere annulata. + Sei sicuro di voler eliminare tutte le notifiche\? L\'azione non puó essere annullata. Risultati sondaggio Vuoi uscire senza salvare l\'immagine\? Permessi non abilitati! @@ -901,4 +901,18 @@ Mostra comunque L\'app non é riuscita ad aggiungere l\'account nella lista! Elimina timeline + Riprova piú tardi. + Chiaro - colori personalizzati + Scuro - Colori personalizzati + La conversazione é iniziata sulla tua istanza! + L\'app non ha trovato il messaggio remoto. + Account suggeribile + Imposta colori personalizzati + Silenzia etichetta + Non silenziare etichetta + Fissa etichetta + Non fissare etichetta + Non seguire etichetta + Selettore lingua + Mostra conversazione remota \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 90a50a64..62dd6db2 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -4,7 +4,7 @@