Merge branch 'maintenance'
This commit is contained in:
commit
b022c55239
|
@ -7,7 +7,7 @@ buildscript {
|
|||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ allprojects {
|
|||
|
||||
ext {
|
||||
projectGroupId = 'org.mariotaku.twidere'
|
||||
projectVersionCode = 507
|
||||
projectVersionName = '4.0.8'
|
||||
projectVersionCode = 508
|
||||
projectVersionName = '4.0.9'
|
||||
|
||||
globalCompileSdkVersion = 29
|
||||
globalBuildToolsVersion = '29.0.2'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Thu Oct 24 17:45:27 JST 2019
|
||||
#Mon Apr 20 10:36:29 CEST 2020
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
@ -144,7 +144,7 @@ android {
|
|||
exclude 'jsr305_annotations/**'
|
||||
exclude 'error_prone/**'
|
||||
exclude 'third_party/java_src/**'
|
||||
exclude 'sdk-version.txt'
|
||||
exclude 'sdk-version.txt'
|
||||
exclude 'build-data.properties'
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.content.DialogInterface;
|
|||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnMultiChoiceClickListener;
|
||||
import android.content.DialogInterface.OnShowListener;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -157,7 +158,7 @@ public final class DataExportImportTypeSelectorDialogFragment extends BaseDialog
|
|||
final FragmentActivity a = getActivity();
|
||||
final Bundle args = getArguments();
|
||||
if (args == null) return;
|
||||
final String path = args.getString(EXTRA_PATH);
|
||||
final Uri path = args.getParcelable(EXTRA_PATH);
|
||||
if (a instanceof Callback) {
|
||||
((Callback) a).onPositiveButtonClicked(path, flags);
|
||||
}
|
||||
|
@ -171,7 +172,7 @@ public final class DataExportImportTypeSelectorDialogFragment extends BaseDialog
|
|||
}
|
||||
|
||||
public interface Callback extends ISupportDialogFragmentCallback {
|
||||
void onPositiveButtonClicked(String path, int flags);
|
||||
void onPositiveButtonClicked(Uri path, int flags);
|
||||
}
|
||||
|
||||
private static class Type {
|
||||
|
|
|
@ -26,8 +26,10 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import com.bluelinelabs.logansquare.LoganSquare;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
@ -45,17 +47,19 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Filters;
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.Tabs;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class DataImportExportUtils implements Constants {
|
||||
|
@ -79,9 +83,8 @@ public class DataImportExportUtils implements Constants {
|
|||
| FLAG_HOST_MAPPING | FLAG_KEYBOARD_SHORTCUTS | FLAG_FILTERS | FLAG_TABS;
|
||||
|
||||
@WorkerThread
|
||||
public static void exportData(final Context context, @NonNull final File dst, final int flags) throws IOException {
|
||||
dst.delete();
|
||||
try (FileOutputStream fos = new FileOutputStream(dst);
|
||||
public static void exportData(final Context context, @NonNull final DocumentFile dst, final int flags) throws IOException {
|
||||
try (OutputStream fos = context.getContentResolver().openOutputStream(dst.getUri());
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
if (hasFlag(flags, FLAG_PREFERENCES)) {
|
||||
exportSharedPreferencesData(zos, context, SHARED_PREFERENCES_NAME, ENTRY_PREFERENCES,
|
||||
|
@ -161,104 +164,122 @@ public class DataImportExportUtils implements Constants {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
public static int getImportedSettingsFlags(@NonNull final File src) throws IOException {
|
||||
try (ZipFile zipFile = new ZipFile(src)) {
|
||||
public static int getImportedSettingsFlags(@NonNull final Context context, @NonNull final DocumentFile src) throws IOException {
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(src.getUri());
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||
int flags = 0;
|
||||
if (zipFile.getEntry(ENTRY_PREFERENCES) != null) {
|
||||
List<String> entryNames = new ArrayList<>();
|
||||
ZipEntry entry = null;
|
||||
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||
entryNames.add(entry.getName());
|
||||
}
|
||||
if (entryNames.contains(ENTRY_PREFERENCES)) {
|
||||
flags |= FLAG_PREFERENCES;
|
||||
}
|
||||
if (zipFile.getEntry(ENTRY_NICKNAMES) != null) {
|
||||
if (entryNames.contains(ENTRY_NICKNAMES)) {
|
||||
flags |= FLAG_NICKNAMES;
|
||||
}
|
||||
if (zipFile.getEntry(ENTRY_USER_COLORS) != null) {
|
||||
if (entryNames.contains(ENTRY_USER_COLORS)) {
|
||||
flags |= FLAG_USER_COLORS;
|
||||
}
|
||||
if (zipFile.getEntry(ENTRY_HOST_MAPPING) != null) {
|
||||
if (entryNames.contains(ENTRY_HOST_MAPPING)) {
|
||||
flags |= FLAG_HOST_MAPPING;
|
||||
}
|
||||
if (zipFile.getEntry(ENTRY_KEYBOARD_SHORTCUTS) != null) {
|
||||
if (entryNames.contains(ENTRY_KEYBOARD_SHORTCUTS)) {
|
||||
flags |= FLAG_KEYBOARD_SHORTCUTS;
|
||||
}
|
||||
if (zipFile.getEntry(ENTRY_FILTERS) != null) {
|
||||
if (entryNames.contains(ENTRY_FILTERS)) {
|
||||
flags |= FLAG_FILTERS;
|
||||
}
|
||||
if (zipFile.getEntry(ENTRY_TABS) != null) {
|
||||
if (entryNames.contains(ENTRY_TABS)) {
|
||||
flags |= FLAG_TABS;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
public static void importData(final Context context, final File src, final int flags) throws IOException {
|
||||
public static void importData(final Context context, final DocumentFile src, final int flags) throws IOException {
|
||||
if (src == null) throw new FileNotFoundException();
|
||||
try (ZipFile zipFile = new ZipFile(src)) {
|
||||
if (hasFlag(flags, FLAG_PREFERENCES)) {
|
||||
importSharedPreferencesData(zipFile, context, SHARED_PREFERENCES_NAME, ENTRY_PREFERENCES,
|
||||
new PreferencesExporterStrategy(SharedPreferenceConstants.class));
|
||||
}
|
||||
if (hasFlag(flags, FLAG_NICKNAMES)) {
|
||||
importSharedPreferencesData(zipFile, context, USER_NICKNAME_PREFERENCES_NAME, ENTRY_NICKNAMES,
|
||||
ConvertToStringProcessStrategy.SINGLETON);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_USER_COLORS)) {
|
||||
importSharedPreferencesData(zipFile, context, USER_COLOR_PREFERENCES_NAME, ENTRY_USER_COLORS,
|
||||
ConvertToIntProcessStrategy.SINGLETON);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_HOST_MAPPING)) {
|
||||
importSharedPreferencesData(zipFile, context, HOST_MAPPING_PREFERENCES_NAME, ENTRY_HOST_MAPPING,
|
||||
ConvertToStringProcessStrategy.SINGLETON);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_KEYBOARD_SHORTCUTS)) {
|
||||
importSharedPreferencesData(zipFile, context, KEYBOARD_SHORTCUTS_PREFERENCES_NAME,
|
||||
ENTRY_KEYBOARD_SHORTCUTS, ConvertToStringProcessStrategy.SINGLETON);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_FILTERS)) {
|
||||
importItem(context, zipFile, ENTRY_FILTERS, FiltersData.class, new ContentResolverProcessStrategy<FiltersData>() {
|
||||
@Override
|
||||
public boolean importItem(ContentResolver cr, FiltersData filtersData) throws IOException {
|
||||
if (filtersData == null) return false;
|
||||
insertBase(cr, Filters.Keywords.CONTENT_URI, filtersData.getKeywords());
|
||||
insertBase(cr, Filters.Sources.CONTENT_URI, filtersData.getSources());
|
||||
insertBase(cr, Filters.Links.CONTENT_URI, filtersData.getLinks());
|
||||
insertUser(cr, Filters.Users.CONTENT_URI, filtersData.getUsers());
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(src.getUri());
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream)
|
||||
) {
|
||||
ZipEntry entry = null;
|
||||
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
int read = 0;
|
||||
while ((read = zipInputStream.read(buffer, 0, 1024)) >= 0) {
|
||||
stringBuilder.append(new String(buffer, 0, read));
|
||||
}
|
||||
String data = stringBuilder.toString();
|
||||
if (hasFlag(flags, FLAG_PREFERENCES)) {
|
||||
importSharedPreferencesData(entry, context, SHARED_PREFERENCES_NAME, ENTRY_PREFERENCES,
|
||||
new PreferencesExporterStrategy(SharedPreferenceConstants.class), data);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_NICKNAMES)) {
|
||||
importSharedPreferencesData(entry, context, USER_NICKNAME_PREFERENCES_NAME, ENTRY_NICKNAMES,
|
||||
ConvertToStringProcessStrategy.SINGLETON, data);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_USER_COLORS)) {
|
||||
importSharedPreferencesData(entry, context, USER_COLOR_PREFERENCES_NAME, ENTRY_USER_COLORS,
|
||||
ConvertToIntProcessStrategy.SINGLETON, data);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_HOST_MAPPING)) {
|
||||
importSharedPreferencesData(entry, context, HOST_MAPPING_PREFERENCES_NAME, ENTRY_HOST_MAPPING,
|
||||
ConvertToStringProcessStrategy.SINGLETON, data);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_KEYBOARD_SHORTCUTS)) {
|
||||
importSharedPreferencesData(entry, context, KEYBOARD_SHORTCUTS_PREFERENCES_NAME,
|
||||
ENTRY_KEYBOARD_SHORTCUTS, ConvertToStringProcessStrategy.SINGLETON, data);
|
||||
}
|
||||
if (hasFlag(flags, FLAG_FILTERS)) {
|
||||
importItem(context, entry, ENTRY_FILTERS, FiltersData.class, data, new ContentResolverProcessStrategy<FiltersData>() {
|
||||
@Override
|
||||
public boolean importItem(ContentResolver cr, FiltersData filtersData) throws IOException {
|
||||
if (filtersData == null) return false;
|
||||
insertBase(cr, Filters.Keywords.CONTENT_URI, filtersData.getKeywords());
|
||||
insertBase(cr, Filters.Sources.CONTENT_URI, filtersData.getSources());
|
||||
insertBase(cr, Filters.Links.CONTENT_URI, filtersData.getLinks());
|
||||
insertUser(cr, Filters.Users.CONTENT_URI, filtersData.getUsers());
|
||||
return true;
|
||||
}
|
||||
|
||||
void insertBase(ContentResolver cr, Uri uri, List<FiltersData.BaseItem> items) throws IOException {
|
||||
if (items == null) return;
|
||||
final ObjectCursor.ValuesCreator<FiltersData.BaseItem> baseItemCreator =
|
||||
ObjectCursor.valuesCreatorFrom(FiltersData.BaseItem.class);
|
||||
List<ContentValues> values = new ArrayList<>(items.size());
|
||||
for (FiltersData.BaseItem item : items) {
|
||||
values.add(baseItemCreator.create(item));
|
||||
}
|
||||
ContentResolverUtils.bulkInsert(cr, uri, values);
|
||||
}
|
||||
|
||||
void insertUser(ContentResolver cr, Uri uri, List<FiltersData.UserItem> items) throws IOException {
|
||||
if (items == null) return;
|
||||
final ObjectCursor.ValuesCreator<FiltersData.UserItem> userItemCreator =
|
||||
ObjectCursor.valuesCreatorFrom(FiltersData.UserItem.class);
|
||||
List<ContentValues> values = new ArrayList<>(items.size());
|
||||
for (FiltersData.UserItem item : items) {
|
||||
values.add(userItemCreator.create(item));
|
||||
}
|
||||
ContentResolverUtils.bulkInsert(cr, uri, values);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (hasFlag(flags, FLAG_TABS)) {
|
||||
final ObjectCursor.ValuesCreator<Tab> creator = ObjectCursor.valuesCreatorFrom(Tab.class);
|
||||
importItemsList(context, entry, ENTRY_TABS, Tab.class, data, (cr, items) -> {
|
||||
if (items == null) return false;
|
||||
List<ContentValues> values = new ArrayList<>(items.size());
|
||||
for (Tab item : items) {
|
||||
values.add(creator.create(item));
|
||||
}
|
||||
cr.delete(Tabs.CONTENT_URI, null, null);
|
||||
ContentResolverUtils.bulkInsert(cr, Tabs.CONTENT_URI, values);
|
||||
return true;
|
||||
}
|
||||
|
||||
void insertBase(ContentResolver cr, Uri uri, List<FiltersData.BaseItem> items) throws IOException {
|
||||
if (items == null) return;
|
||||
final ObjectCursor.ValuesCreator<FiltersData.BaseItem> baseItemCreator =
|
||||
ObjectCursor.valuesCreatorFrom(FiltersData.BaseItem.class);
|
||||
List<ContentValues> values = new ArrayList<>(items.size());
|
||||
for (FiltersData.BaseItem item : items) {
|
||||
values.add(baseItemCreator.create(item));
|
||||
}
|
||||
ContentResolverUtils.bulkInsert(cr, uri, values);
|
||||
}
|
||||
|
||||
void insertUser(ContentResolver cr, Uri uri, List<FiltersData.UserItem> items) throws IOException {
|
||||
if (items == null) return;
|
||||
final ObjectCursor.ValuesCreator<FiltersData.UserItem> userItemCreator =
|
||||
ObjectCursor.valuesCreatorFrom(FiltersData.UserItem.class);
|
||||
List<ContentValues> values = new ArrayList<>(items.size());
|
||||
for (FiltersData.UserItem item : items) {
|
||||
values.add(userItemCreator.create(item));
|
||||
}
|
||||
ContentResolverUtils.bulkInsert(cr, uri, values);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (hasFlag(flags, FLAG_TABS)) {
|
||||
final ObjectCursor.ValuesCreator<Tab> creator = ObjectCursor.valuesCreatorFrom(Tab.class);
|
||||
importItemsList(context, zipFile, ENTRY_TABS, Tab.class, (cr, items) -> {
|
||||
if (items == null) return false;
|
||||
List<ContentValues> values = new ArrayList<>(items.size());
|
||||
for (Tab item : items) {
|
||||
values.add(creator.create(item));
|
||||
}
|
||||
cr.delete(Tabs.CONTENT_URI, null, null);
|
||||
ContentResolverUtils.bulkInsert(cr, Tabs.CONTENT_URI, values);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,12 +288,12 @@ public class DataImportExportUtils implements Constants {
|
|||
return (flags & flag) != 0;
|
||||
}
|
||||
|
||||
private static void importSharedPreferencesData(@NonNull final ZipFile zipFile, @NonNull final Context context,
|
||||
private static void importSharedPreferencesData(@NonNull final ZipEntry entry, @NonNull final Context context,
|
||||
@NonNull final String preferencesName, @NonNull final String entryName,
|
||||
@NonNull final SharedPreferencesProcessStrategy strategy) throws IOException {
|
||||
final ZipEntry entry = zipFile.getEntry(entryName);
|
||||
if (entry == null) return;
|
||||
final JsonParser jsonParser = LoganSquare.JSON_FACTORY.createParser(zipFile.getInputStream(entry));
|
||||
@NonNull final SharedPreferencesProcessStrategy strategy,
|
||||
@NonNull final String data) throws IOException {
|
||||
if (!Objects.equals(entry.getName(), entryName)) return;
|
||||
final JsonParser jsonParser = LoganSquare.JSON_FACTORY.createParser(data);
|
||||
if (jsonParser.getCurrentToken() == null) {
|
||||
jsonParser.nextToken();
|
||||
}
|
||||
|
@ -300,14 +321,14 @@ public class DataImportExportUtils implements Constants {
|
|||
}
|
||||
|
||||
private static <T> void importItemsList(@NonNull final Context context,
|
||||
@NonNull final ZipFile zipFile,
|
||||
@NonNull final ZipEntry entry,
|
||||
@NonNull final String entryName,
|
||||
@NonNull final Class<T> itemCls,
|
||||
@NonNull final String data,
|
||||
@NonNull final ContentResolverProcessStrategy<List<T>> strategy)
|
||||
throws IOException {
|
||||
final ZipEntry entry = zipFile.getEntry(entryName);
|
||||
if (entry == null) return;
|
||||
List<T> itemsList = JsonSerializer.parseList(zipFile.getInputStream(entry), itemCls);
|
||||
if (!Objects.equals(entry.getName(), entryName)) return;
|
||||
List<T> itemsList = JsonSerializer.parseList(data, itemCls);
|
||||
strategy.importItem(context.getContentResolver(), itemsList);
|
||||
}
|
||||
|
||||
|
@ -317,19 +338,22 @@ public class DataImportExportUtils implements Constants {
|
|||
@NonNull final Class<T> itemCls,
|
||||
@NonNull final List<T> itemList) throws IOException {
|
||||
zos.putNextEntry(new ZipEntry(entryName));
|
||||
JsonSerializer.serialize(itemList, zos, itemCls);
|
||||
String json = LoganSquare.serialize(itemList);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(zos);
|
||||
writer.write(json);
|
||||
writer.flush();
|
||||
zos.closeEntry();
|
||||
}
|
||||
|
||||
private static <T> void importItem(@NonNull final Context context,
|
||||
@NonNull final ZipFile zipFile,
|
||||
@NonNull final ZipEntry entry,
|
||||
@NonNull final String entryName,
|
||||
@NonNull final Class<T> itemCls,
|
||||
@NonNull final String data,
|
||||
@NonNull final ContentResolverProcessStrategy<T> strategy)
|
||||
throws IOException {
|
||||
final ZipEntry entry = zipFile.getEntry(entryName);
|
||||
if (entry == null) return;
|
||||
T item = JsonSerializer.parse(zipFile.getInputStream(entry), itemCls);
|
||||
if (!Objects.equals(entry.getName(), entryName)) return;
|
||||
T item = JsonSerializer.parse(data, itemCls);
|
||||
strategy.importItem(context.getContentResolver(), item);
|
||||
}
|
||||
|
||||
|
@ -339,7 +363,10 @@ public class DataImportExportUtils implements Constants {
|
|||
@NonNull final Class<T> itemCls,
|
||||
@NonNull final T item) throws IOException {
|
||||
zos.putNextEntry(new ZipEntry(entryName));
|
||||
JsonSerializer.serialize(item, zos, itemCls);
|
||||
String json = LoganSquare.serialize(item);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(zos);
|
||||
writer.write(json);
|
||||
writer.flush();
|
||||
zos.closeEntry();
|
||||
}
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ public class MicroBlogAPIFactory implements TwidereConstants {
|
|||
} else if (TextUtils.isEmpty(domain)) {
|
||||
baseUrl = matcher.replaceAll("");
|
||||
} else {
|
||||
baseUrl = matcher.replaceAll("$1" + domain + "." + "$2");
|
||||
baseUrl = matcher.replaceAll("$1" + domain + "$2" + "$3");
|
||||
}
|
||||
// In case someone set invalid base url
|
||||
if (HttpUrl.parse(baseUrl) == null) {
|
||||
|
|
|
@ -20,16 +20,33 @@
|
|||
package androidx.core.os
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import java.util.*
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
object LocaleHelperAccessor {
|
||||
fun forLanguageTag(str: String): Locale {
|
||||
if (str.contains("-")) {
|
||||
val args = str.split("-").dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (args.size > 2) {
|
||||
return Locale(args[0], args[1], args[2])
|
||||
} else if (args.size > 1) {
|
||||
return Locale(args[0], args[1])
|
||||
} else if (args.size == 1) {
|
||||
return Locale(args[0])
|
||||
}
|
||||
} else if (str.contains("_")) {
|
||||
val args = str.split("_").dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (args.size > 2) {
|
||||
return Locale(args[0], args[1], args[2])
|
||||
} else if (args.size > 1) {
|
||||
return Locale(args[0], args[1])
|
||||
} else if (args.size == 1) {
|
||||
return Locale(args[0])
|
||||
}
|
||||
} else {
|
||||
return Locale(str)
|
||||
}
|
||||
|
||||
fun forLanguageTag(str: String): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Locale.forLanguageTag(str)
|
||||
} else {
|
||||
Locale(str)// TODO: Dose it work?
|
||||
// TODO("VERSION.SDK_INT < LOLLIPOP")
|
||||
throw IllegalArgumentException("Can not parse language tag: [$str]")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,13 @@ package org.mariotaku.twidere.activity
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import org.mariotaku.ktextension.dismissDialogFragment
|
||||
import org.mariotaku.twidere.Constants.*
|
||||
import org.mariotaku.twidere.R
|
||||
|
@ -39,10 +42,10 @@ class DataExportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
REQUEST_PICK_DIRECTORY -> {
|
||||
executeAfterFragmentResumed {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
val path = data.data?.path
|
||||
val path = data.data
|
||||
val df = DataExportImportTypeSelectorDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putString(EXTRA_PATH, path)
|
||||
args.putParcelable(EXTRA_PATH, path)
|
||||
args.putString(EXTRA_TITLE, getString(R.string.export_settings_type_dialog_title))
|
||||
df.arguments = args
|
||||
df.show(supportFragmentManager, "select_export_type")
|
||||
|
@ -58,13 +61,18 @@ class DataExportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onPositiveButtonClicked(path: String?, flags: Int) {
|
||||
override fun onPositiveButtonClicked(path: Uri?, flags: Int) {
|
||||
if (path == null || flags == 0) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
if (task == null || task!!.status != AsyncTask.Status.RUNNING) {
|
||||
task = ExportSettingsTask(this, path, flags)
|
||||
val folder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
DocumentFile.fromTreeUri(this, path)
|
||||
} else {
|
||||
DocumentFile.fromFile(File(path.path))
|
||||
}
|
||||
task = ExportSettingsTask(this, folder, flags)
|
||||
task!!.execute()
|
||||
}
|
||||
}
|
||||
|
@ -85,16 +93,18 @@ class DataExportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
|
||||
internal class ExportSettingsTask(
|
||||
private val activity: DataExportActivity,
|
||||
private val path: String?,
|
||||
private val folder: DocumentFile?,
|
||||
private val flags: Int
|
||||
) : AsyncTask<Any, Any, Boolean>() {
|
||||
|
||||
override fun doInBackground(vararg params: Any): Boolean? {
|
||||
if (path == null) return false
|
||||
if (folder == null || !folder.isDirectory) return false
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US)
|
||||
val fileName = String.format("Twidere_Settings_%s.zip", sdf.format(Date()))
|
||||
val file = File(path, fileName)
|
||||
file.delete()
|
||||
val file = folder.findFile(fileName) ?: folder.createFile("application/zip", fileName)
|
||||
?: return false
|
||||
// val file = File(folder, fileName)
|
||||
// file.delete()
|
||||
try {
|
||||
DataImportExportUtils.exportData(activity, file, flags)
|
||||
return true
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.mariotaku.twidere.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import org.mariotaku.ktextension.dismissDialogFragment
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.*
|
||||
|
@ -37,9 +40,14 @@ class DataImportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
REQUEST_PICK_FILE -> {
|
||||
resumeFragmentsRunnable = Runnable {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
val path = data.data?.path
|
||||
val path = data.data!!
|
||||
if (openImportTypeTask == null || openImportTypeTask!!.status != AsyncTask.Status.RUNNING) {
|
||||
openImportTypeTask = OpenImportTypeTask(this@DataImportActivity, path)
|
||||
val file = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
DocumentFile.fromSingleUri(this, path)
|
||||
} else {
|
||||
DocumentFile.fromFile(File(path.path))
|
||||
}
|
||||
openImportTypeTask = OpenImportTypeTask(this@DataImportActivity, file)
|
||||
openImportTypeTask!!.execute()
|
||||
}
|
||||
} else {
|
||||
|
@ -62,13 +70,18 @@ class DataImportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
}
|
||||
}
|
||||
|
||||
override fun onPositiveButtonClicked(path: String?, flags: Int) {
|
||||
override fun onPositiveButtonClicked(path: Uri?, flags: Int) {
|
||||
if (path == null || flags == 0) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
if (importSettingsTask == null || importSettingsTask!!.status != AsyncTask.Status.RUNNING) {
|
||||
importSettingsTask = ImportSettingsTask(this, path, flags)
|
||||
val file = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
DocumentFile.fromSingleUri(this, path)
|
||||
} else {
|
||||
DocumentFile.fromFile(File(path.path))
|
||||
}
|
||||
importSettingsTask = ImportSettingsTask(this, file, flags)
|
||||
importSettingsTask!!.execute()
|
||||
}
|
||||
}
|
||||
|
@ -89,13 +102,14 @@ class DataImportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
|
||||
internal class ImportSettingsTask(
|
||||
private val activity: DataImportActivity,
|
||||
private val path: String?,
|
||||
private val file: DocumentFile?,
|
||||
private val flags: Int
|
||||
) : AsyncTask<Any, Any, Boolean>() {
|
||||
|
||||
override fun doInBackground(vararg params: Any): Boolean? {
|
||||
if (path == null) return false
|
||||
val file = File(path)
|
||||
if (file == null) {
|
||||
return false
|
||||
}
|
||||
if (!file.isFile) return false
|
||||
try {
|
||||
DataImportExportUtils.importData(activity, file, flags)
|
||||
|
@ -131,14 +145,15 @@ class DataImportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
|
||||
}
|
||||
|
||||
internal class OpenImportTypeTask(private val activity: DataImportActivity, private val path: String?) : AsyncTask<Any, Any, Int>() {
|
||||
internal class OpenImportTypeTask(private val activity: DataImportActivity, private val file: DocumentFile?) : AsyncTask<Any, Any, Int>() {
|
||||
|
||||
override fun doInBackground(vararg params: Any): Int? {
|
||||
if (path == null) return 0
|
||||
val file = File(path)
|
||||
if (file == null) {
|
||||
return 0
|
||||
}
|
||||
if (!file.isFile) return 0
|
||||
try {
|
||||
return DataImportExportUtils.getImportedSettingsFlags(file)
|
||||
return DataImportExportUtils.getImportedSettingsFlags(activity, file)
|
||||
} catch (e: IOException) {
|
||||
return 0
|
||||
}
|
||||
|
@ -151,7 +166,7 @@ class DataImportActivity : BaseActivity(), DataExportImportTypeSelectorDialogFra
|
|||
}
|
||||
val df = DataExportImportTypeSelectorDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putString(EXTRA_PATH, path)
|
||||
args.putParcelable(EXTRA_PATH, file?.uri)
|
||||
args.putString(EXTRA_TITLE, activity.getString(R.string.import_settings_type_dialog_title))
|
||||
if (flags != null) {
|
||||
args.putInt(EXTRA_FLAGS, flags)
|
||||
|
|
|
@ -25,9 +25,9 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment.getExternalStorageDirectory
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import android.widget.Toast
|
||||
import org.mariotaku.ktextension.Bundle
|
||||
import org.mariotaku.ktextension.checkAllSelfPermissionsGranted
|
||||
import org.mariotaku.ktextension.set
|
||||
|
@ -40,6 +40,8 @@ import android.Manifest.permission as AndroidPermissions
|
|||
|
||||
class FileSelectorActivity : BaseActivity(), FileSelectorDialogFragment.Callback {
|
||||
|
||||
private val PICKER_REQUEST_CODE: Int = 54837
|
||||
|
||||
override fun onCancelled(df: DialogFragment) {
|
||||
if (!isFinishing) {
|
||||
finish()
|
||||
|
@ -65,7 +67,7 @@ class FileSelectorActivity : BaseActivity(), FileSelectorDialogFragment.Callback
|
|||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == REQUEST_REQUEST_PERMISSIONS) {
|
||||
executeAfterFragmentResumed {
|
||||
|
@ -101,15 +103,44 @@ class FileSelectorActivity : BaseActivity(), FileSelectorDialogFragment.Callback
|
|||
finish()
|
||||
}
|
||||
|
||||
private fun showPickFileDialog() {
|
||||
val initialDirectory = intent?.data?.path?.let(::File) ?: getExternalStorageDirectory() ?: File("/")
|
||||
val f = FileSelectorDialogFragment()
|
||||
f.arguments = Bundle {
|
||||
this[EXTRA_ACTION] = intent.action
|
||||
this[EXTRA_PATH] = initialDirectory.absolutePath
|
||||
this[EXTRA_FILE_EXTENSIONS] = intent.getStringArrayExtra(EXTRA_FILE_EXTENSIONS)
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == PICKER_REQUEST_CODE && resultCode == Activity.RESULT_OK && data != null) {
|
||||
setResult(Activity.RESULT_OK, Intent().also { it.data = data.data })
|
||||
finish()
|
||||
} else {
|
||||
if (!isFinishing) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPickFileDialog() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
Intent().apply {
|
||||
if (intent.action == INTENT_ACTION_PICK_FILE) {
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
type = "*/*"
|
||||
} else if (intent.action == INTENT_ACTION_PICK_DIRECTORY) {
|
||||
action = Intent.ACTION_OPEN_DOCUMENT_TREE
|
||||
}
|
||||
}.also {
|
||||
startActivityForResult(
|
||||
Intent.createChooser(it, getString(R.string.pick_file)),
|
||||
PICKER_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val initialDirectory = intent?.data?.path?.let(::File) ?: getExternalStorageDirectory()
|
||||
?: File("/")
|
||||
val f = FileSelectorDialogFragment()
|
||||
f.arguments = Bundle {
|
||||
this[EXTRA_ACTION] = intent.action
|
||||
this[EXTRA_PATH] = initialDirectory.absolutePath
|
||||
this[EXTRA_FILE_EXTENSIONS] = intent.getStringArrayExtra(EXTRA_FILE_EXTENSIONS)
|
||||
}
|
||||
f.show(supportFragmentManager, "select_file")
|
||||
}
|
||||
f.show(supportFragmentManager, "select_file")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@ val AccountExtras.official: Boolean
|
|||
return false
|
||||
}
|
||||
|
||||
val AccountDetails.hasDm: Boolean
|
||||
get() = type in arrayOf(AccountType.FANFOU, AccountType.TWITTER)
|
||||
|
||||
fun <T> AccountDetails.newMicroBlogInstance(context: Context, cls: Class<T>): T {
|
||||
return credentials.newMicroBlogInstance(context, type, cls)
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ class SearchFragment : AbsToolbarTabPagesFragment(), RefreshScrollTopInterface,
|
|||
customView.setOnClickListener {
|
||||
val searchIntent = Intent(context, QuickSearchBarActivity::class.java).apply {
|
||||
putExtra(EXTRA_QUERY, query)
|
||||
putExtra(EXTRA_ACCOUNT_KEY, accountKey)
|
||||
}
|
||||
startActivityForResult(searchIntent, REQUEST_OPEN_SEARCH)
|
||||
}
|
||||
|
|
|
@ -43,8 +43,6 @@ class SaveMediaToGalleryTask(
|
|||
override fun onFileSaved(savedFile: File, mimeType: String?) {
|
||||
val context = context ?: return
|
||||
|
||||
MediaScannerConnection.scanFile(context, arrayOf(savedFile.path),
|
||||
arrayOf(mimeType), null)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val type = (fileInfo as? CacheProvider.CacheFileTypeSupport)?.cacheFileType
|
||||
val path = when (type) {
|
||||
|
@ -80,9 +78,14 @@ class SaveMediaToGalleryTask(
|
|||
fileInputStream.copyTo(it)
|
||||
}
|
||||
}
|
||||
MediaScannerConnection.scanFile(context, arrayOf(uri.path),
|
||||
arrayOf(fileInfo.mimeType), null)
|
||||
}
|
||||
savedFile.delete()
|
||||
} else {
|
||||
MediaScannerConnection.scanFile(context, arrayOf(savedFile.path),
|
||||
arrayOf(fileInfo.mimeType), null)
|
||||
}
|
||||
savedFile.delete()
|
||||
Toast.makeText(context, R.string.message_toast_saved_to_gallery, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
|
|
@ -22,24 +22,22 @@ package org.mariotaku.twidere.task.twitter.message
|
|||
import android.accounts.AccountManager
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import org.mariotaku.commons.logansquare.LoganSquareMapperFinder
|
||||
import org.mariotaku.ktextension.*
|
||||
import org.mariotaku.ktextension.mapToArray
|
||||
import org.mariotaku.ktextension.toIntOr
|
||||
import org.mariotaku.ktextension.toLongOr
|
||||
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||
import org.mariotaku.microblog.library.MicroBlog
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.model.DMResponse
|
||||
import org.mariotaku.microblog.library.twitter.model.DirectMessage
|
||||
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||
import org.mariotaku.restfu.callback.RawCallback
|
||||
import org.mariotaku.restfu.http.HttpResponse
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_SHOW_NOTIFICATION
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.extension.findFieldByTypes
|
||||
import org.mariotaku.twidere.exception.APINotSupportedException
|
||||
import org.mariotaku.twidere.extension.model.*
|
||||
import org.mariotaku.twidere.extension.model.api.target
|
||||
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||
import org.mariotaku.twidere.extension.queryCount
|
||||
import org.mariotaku.twidere.extension.queryReference
|
||||
|
@ -61,8 +59,11 @@ import org.mariotaku.twidere.task.BaseAbstractTask
|
|||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.UriUtils
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
||||
import java.lang.Exception
|
||||
import java.util.*
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.filter
|
||||
import kotlin.collections.set
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/8.
|
||||
|
@ -85,6 +86,7 @@ class GetMessagesTask(
|
|||
}
|
||||
val microBlog = details.newMicroBlogInstance(context, cls = MicroBlog::class.java)
|
||||
val messages = try {
|
||||
if (!details.hasDm) throw APINotSupportedException(details.type)
|
||||
getMessages(microBlog, details, param, i)
|
||||
} catch (e: MicroBlogException) {
|
||||
return@forEachIndexed
|
||||
|
@ -163,7 +165,7 @@ class GetMessagesTask(
|
|||
directMessage[DirectMessage::class.java.getDeclaredField("text")] = it.messageCreate.messageData.text
|
||||
directMessage[DirectMessage::class.java.getDeclaredField("id")] = it.id
|
||||
directMessage[DirectMessage::class.java.getDeclaredField("sender")] = users.firstOrNull { user -> it.messageCreate.senderId == user.id }
|
||||
directMessage[DirectMessage::class.java.getDeclaredField("recipient")] = users.firstOrNull { user -> it.messageCreate.senderId == user.id }
|
||||
directMessage[DirectMessage::class.java.getDeclaredField("recipient")] = users.firstOrNull { user -> it.messageCreate.target.recipientId == user.id }
|
||||
directMessage[DirectMessage::class.java.getDeclaredField("createdAt")] = Date(it.createdTimestamp.toLong())
|
||||
}
|
||||
}.filter { it.sender != null }
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Twidere.Dark" parent="Theme.MaterialComponents">
|
||||
<style name="Theme.Twidere.Dark" parent="Theme.MaterialComponents.Bridge">
|
||||
|
||||
<!-- Widget styles -->
|
||||
<item name="android:listSeparatorTextViewStyle">@style/Widget.Dark.TextView.ListSeparator
|
||||
|
@ -47,7 +47,7 @@
|
|||
|
||||
<style name="Theme.Twidere.Dark.Content" parent="Theme.Twidere.Dark"/>
|
||||
|
||||
<style name="Theme.Twidere.Dark.Dialog" parent="Theme.MaterialComponents.Dialog">
|
||||
<style name="Theme.Twidere.Dark.Dialog" parent="Theme.MaterialComponents.Dialog.Bridge">
|
||||
|
||||
<!-- this is a simple fix for https://github.com/TwidereProject/Twidere-Android/issues/579 -->
|
||||
<item name="android:windowBackground">@drawable/abc_dialog_material_background_dark</item>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Twidere.Light" parent="Theme.MaterialComponents.Light">
|
||||
<style name="Theme.Twidere.Light" parent="Theme.MaterialComponents.Light.Bridge">
|
||||
|
||||
<!-- Widget styles -->
|
||||
<item name="android:listSeparatorTextViewStyle">@style/Widget.Light.TextView.ListSeparator
|
||||
|
@ -51,7 +51,7 @@
|
|||
|
||||
<style name="Theme.Twidere.Light.Content" parent="Theme.Twidere.Light"/>
|
||||
|
||||
<style name="Theme.Twidere.Light.Dialog" parent="Theme.MaterialComponents.Light.Dialog">
|
||||
<style name="Theme.Twidere.Light.Dialog" parent="Theme.MaterialComponents.Light.Dialog.Bridge">
|
||||
|
||||
<!-- Widget styles -->
|
||||
<item name="android:listSeparatorTextViewStyle">@style/Widget.Light.TextView.ListSeparator
|
||||
|
|
Loading…
Reference in New Issue