Merge branch 'maintenance'

This commit is contained in:
Tlaster 2020-04-21 17:31:20 +08:00
commit b022c55239
16 changed files with 271 additions and 161 deletions

View File

@ -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'

View File

@ -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

View File

@ -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'
}

View File

@ -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 {

View File

@ -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();
}

View File

@ -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) {

View File

@ -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]")
}
}

View File

@ -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

View File

@ -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)

View File

@ -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")
}
}

View 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)
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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 }

View File

@ -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>

View File

@ -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