|
|
|
|
@@ -3,6 +3,11 @@ package org.joinmastodon.android.fragments.settings;
|
|
|
|
|
import android.app.Activity;
|
|
|
|
|
import android.content.ClipData;
|
|
|
|
|
import android.content.ClipboardManager;
|
|
|
|
|
import android.content.ComponentName;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
|
import android.content.pm.PackageManager;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.Build;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
@@ -11,6 +16,13 @@ import android.view.ViewGroup;
|
|
|
|
|
import android.widget.TextView;
|
|
|
|
|
import android.widget.Toast;
|
|
|
|
|
|
|
|
|
|
import com.google.gson.Gson;
|
|
|
|
|
import com.google.gson.GsonBuilder;
|
|
|
|
|
import com.google.gson.JsonElement;
|
|
|
|
|
import com.google.gson.JsonObject;
|
|
|
|
|
import com.google.gson.JsonParser;
|
|
|
|
|
import com.google.gson.ToNumberPolicy;
|
|
|
|
|
|
|
|
|
|
import org.joinmastodon.android.BuildConfig;
|
|
|
|
|
import org.joinmastodon.android.GlobalUserPreferences;
|
|
|
|
|
import org.joinmastodon.android.MastodonApp;
|
|
|
|
|
@@ -21,27 +33,26 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
|
|
|
import org.joinmastodon.android.fragments.HasAccountID;
|
|
|
|
|
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
|
|
|
|
import org.joinmastodon.android.model.viewmodel.ListItem;
|
|
|
|
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
|
|
|
import org.joinmastodon.android.ui.Snackbar;
|
|
|
|
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
|
|
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
|
|
|
|
import org.joinmastodon.android.utils.FileProvider;
|
|
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
|
import java.io.FileWriter;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
|
import java.io.StringReader;
|
|
|
|
|
import java.nio.charset.Charset;
|
|
|
|
|
import java.nio.file.Files;
|
|
|
|
|
import java.time.Instant;
|
|
|
|
|
import java.time.ZoneId;
|
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
import java.time.format.FormatStyle;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
|
import me.grishka.appkit.imageloader.ImageCache;
|
|
|
|
|
@@ -51,6 +62,7 @@ import me.grishka.appkit.utils.V;
|
|
|
|
|
|
|
|
|
|
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
|
|
|
|
private static final String TAG="SettingsAboutAppFragment";
|
|
|
|
|
private static final int IMPORT_RESULT=314;
|
|
|
|
|
private ListItem<Void> mediaCacheItem, copyCrashLogItem;
|
|
|
|
|
private CheckableListItem<Void> enablePreReleasesItem;
|
|
|
|
|
private AccountSession session;
|
|
|
|
|
@@ -58,7 +70,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
|
|
|
|
|
private File crashLogFile=new File(MastodonApp.context.getFilesDir(), "crash.log");
|
|
|
|
|
|
|
|
|
|
// MOSHIDON
|
|
|
|
|
private ListItem<Void> clearRecentEmojisItem;
|
|
|
|
|
private ListItem<Void> clearRecentEmojisItem, exportItem, importItem;
|
|
|
|
|
@Override
|
|
|
|
|
public void onCreate(Bundle savedInstanceState){
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
@@ -73,6 +85,8 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
|
|
|
|
|
new ListItem<>(R.string.mo_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.mo_repo_url))),
|
|
|
|
|
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")),
|
|
|
|
|
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
|
|
|
|
|
exportItem=new ListItem<>(R.string.export_settings_title, R.string.export_settings_summary, R.drawable.ic_fluent_arrow_export_24_filled, this::onExportClick),
|
|
|
|
|
importItem=new ListItem<>(R.string.import_settings_title, R.string.import_settings_summary, R.drawable.ic_fluent_arrow_import_24_filled, this::onImportClick, 0, true),
|
|
|
|
|
clearRecentEmojisItem=new ListItem<>(R.string.mo_clear_recent_emoji, 0, this::onClearRecentEmojisClick),
|
|
|
|
|
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick),
|
|
|
|
|
new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick),
|
|
|
|
|
@@ -146,6 +160,149 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
|
|
|
|
|
Toast.makeText(getContext(), R.string.mo_recent_emoji_cleared, Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void onExportClick(ListItem<?> item){
|
|
|
|
|
Gson gson = new Gson();
|
|
|
|
|
JsonObject jsonObject = new JsonObject();
|
|
|
|
|
jsonObject.addProperty("versionName", BuildConfig.VERSION_NAME);
|
|
|
|
|
jsonObject.addProperty("versionCode", BuildConfig.VERSION_CODE);
|
|
|
|
|
|
|
|
|
|
// GlobalUserPreferences
|
|
|
|
|
//TODO: remove prefs that should not be exported
|
|
|
|
|
JsonElement je = gson.toJsonTree(GlobalUserPreferences.getPrefs().getAll());
|
|
|
|
|
jsonObject.add("GlobalUserPreferences", je);
|
|
|
|
|
|
|
|
|
|
// add account local prefs
|
|
|
|
|
for(AccountSession accountSession: AccountSessionManager.getInstance().getLoggedInAccounts()) {
|
|
|
|
|
Map<String, ?> prefs = accountSession.getRawLocalPreferences().getAll();
|
|
|
|
|
//TODO: remove prefs that should not be exported
|
|
|
|
|
JsonElement accountPrefs = gson.toJsonTree(prefs);
|
|
|
|
|
jsonObject.add(accountSession.self.id, accountPrefs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
File file = new File(getContext().getCacheDir(), "moshidon-exported-settings.json");
|
|
|
|
|
FileWriter writer = new FileWriter(file);
|
|
|
|
|
writer.write(jsonObject.toString());
|
|
|
|
|
writer.flush();
|
|
|
|
|
writer.close();
|
|
|
|
|
|
|
|
|
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
|
|
|
|
intent.setType("application/json");
|
|
|
|
|
Uri outputUri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".fileprovider", file);
|
|
|
|
|
intent.putExtra(Intent.EXTRA_STREAM, outputUri);
|
|
|
|
|
startActivity(Intent.createChooser(intent, getContext().getString(R.string.export_settings_share)));
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Toast.makeText(getContext(), getContext().getString(R.string.export_settings_fail), Toast.LENGTH_SHORT).show();
|
|
|
|
|
Log.w(TAG, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void onImportClick(ListItem<?> item){
|
|
|
|
|
new M3AlertDialogBuilder(getContext())
|
|
|
|
|
.setTitle(R.string.import_settings_confirm)
|
|
|
|
|
.setIcon(R.drawable.ic_fluent_warning_24_regular)
|
|
|
|
|
.setMessage(R.string.import_settings_confirm_body)
|
|
|
|
|
.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
|
|
|
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
|
|
|
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
|
|
|
intent.setType("application/json");
|
|
|
|
|
startActivityForResult(intent, IMPORT_RESULT);
|
|
|
|
|
})
|
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
|
.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
|
|
|
|
if(requestCode==IMPORT_RESULT && resultCode==Activity.RESULT_OK){
|
|
|
|
|
Uri uri=data.getData();
|
|
|
|
|
if(uri==null){
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try{
|
|
|
|
|
InputStream inputStream=getContext().getContentResolver().openInputStream(uri);
|
|
|
|
|
if(inputStream==null)
|
|
|
|
|
return;
|
|
|
|
|
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
|
|
|
|
|
StringBuilder stringBuilder=new StringBuilder();
|
|
|
|
|
String line;
|
|
|
|
|
while((line=reader.readLine())!=null){
|
|
|
|
|
stringBuilder.append(line);
|
|
|
|
|
}
|
|
|
|
|
inputStream.close();
|
|
|
|
|
String jsonString=stringBuilder.toString();
|
|
|
|
|
|
|
|
|
|
Gson gson=new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();
|
|
|
|
|
JsonObject jsonObject=JsonParser.parseString(jsonString).getAsJsonObject();
|
|
|
|
|
|
|
|
|
|
//check if json has required attributes
|
|
|
|
|
if(!(jsonObject.has("versionName") && jsonObject.has("versionCode") && jsonObject.has("GlobalUserPreferences"))){
|
|
|
|
|
Toast.makeText(getContext(), getContext().getString(R.string.import_settings_failed), Toast.LENGTH_SHORT).show();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
String versionName=jsonObject.get("versionName").getAsString();
|
|
|
|
|
int versionCode=jsonObject.get("versionCode").getAsInt();
|
|
|
|
|
Log.i(TAG, "onActivityResult: Reading exported settings ("+versionName+" "+versionCode+")");
|
|
|
|
|
|
|
|
|
|
// retrieve GlobalUserPreferences
|
|
|
|
|
Map<String, ?> jsonGlobalPrefs=gson.fromJson(jsonObject.getAsJsonObject("GlobalUserPreferences"), Map.class);
|
|
|
|
|
SharedPreferences.Editor globalPrefsEditor=GlobalUserPreferences.getPrefs().edit();
|
|
|
|
|
for(String key : jsonGlobalPrefs.keySet()){
|
|
|
|
|
Object value=jsonGlobalPrefs.get(key);
|
|
|
|
|
if(value==null)
|
|
|
|
|
continue;
|
|
|
|
|
savePrefValue(globalPrefsEditor, key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// retrieve LocalPreferences for all logged in accounts
|
|
|
|
|
//TODO: maybe show a dialog for which accounts to import?
|
|
|
|
|
for(AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
|
|
|
|
|
if(!jsonObject.has(accountSession.self.id))
|
|
|
|
|
continue;
|
|
|
|
|
Map<String, ?> prefs=gson.fromJson(jsonObject.getAsJsonObject(accountSession.self.id), Map.class);
|
|
|
|
|
|
|
|
|
|
SharedPreferences.Editor prefEditor=accountSession.getRawLocalPreferences().edit();
|
|
|
|
|
for(String key : prefs.keySet()){
|
|
|
|
|
Object value=prefs.get(key);
|
|
|
|
|
if(value==null)
|
|
|
|
|
continue;
|
|
|
|
|
savePrefValue(prefEditor, key, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// restart app to apply new preferences
|
|
|
|
|
// https://stackoverflow.com/a/46848226
|
|
|
|
|
PackageManager packageManager=getContext().getPackageManager();
|
|
|
|
|
Intent intent=packageManager.getLaunchIntentForPackage(getContext().getPackageName());
|
|
|
|
|
ComponentName componentName=intent.getComponent();
|
|
|
|
|
Intent mainIntent=Intent.makeRestartActivityTask(componentName);
|
|
|
|
|
// Required for API 34 and later
|
|
|
|
|
// Ref: https://developer.android.com/about/versions/14/behavior-changes-14#safer-intents
|
|
|
|
|
mainIntent.setPackage(getContext().getPackageName());
|
|
|
|
|
getContext().startActivity(mainIntent);
|
|
|
|
|
Runtime.getRuntime().exit(0);
|
|
|
|
|
}catch(IOException e){
|
|
|
|
|
Log.w(TAG, e);
|
|
|
|
|
Toast.makeText(getContext(), getContext().getString(R.string.import_settings_failed), Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void savePrefValue(SharedPreferences.Editor editor, String key, Object value) {
|
|
|
|
|
if(value.getClass().equals(Boolean.class))
|
|
|
|
|
editor.putBoolean(key, (Boolean) value);
|
|
|
|
|
// gson parses all numbers either long (for int) or double (the rest)
|
|
|
|
|
else if(value.getClass().equals(Long.class))
|
|
|
|
|
editor.putInt(key, ((Long) value).intValue());
|
|
|
|
|
else if(value.getClass().equals(Double.class))
|
|
|
|
|
editor.putFloat(key, ((Double) value).floatValue());
|
|
|
|
|
else
|
|
|
|
|
editor.putString(key, String.valueOf(value));
|
|
|
|
|
//explicitly immediately since the app will restarted soon after
|
|
|
|
|
// and it may not have the time to write the values in the background
|
|
|
|
|
editor.commit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void updateMediaCacheItem(){
|
|
|
|
|
long size=ImageCache.getInstance(getActivity()).getDiskCache().size();
|
|
|
|
|
mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false);
|
|
|
|
|
|