diff --git a/build.gradle b/build.gradle index 2c05856f05..da54ec5543 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 38a8a7d457..649149d92c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/twidere/build.gradle b/twidere/build.gradle index 82a3adfa02..ec64610eb3 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -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' } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/DataExportImportTypeSelectorDialogFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/DataExportImportTypeSelectorDialogFragment.java index cd4ea42b06..ab3f95d0af 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/DataExportImportTypeSelectorDialogFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/DataExportImportTypeSelectorDialogFragment.java @@ -25,6 +25,7 @@ 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 @@ private void onPositiveButtonClicked(final int flags) { 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 @@ private void updatePositiveButton(final DialogInterface dialog) { } public interface Callback extends ISupportDialogFragmentCallback { - void onPositiveButtonClicked(String path, int flags); + void onPositiveButtonClicked(Uri path, int flags); } private static class Type { diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java index 09ba124a86..65d4dbe699 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java @@ -26,8 +26,10 @@ 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.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 @@ private static List queryAll(ContentResolver cr, Uri uri, String[] projec } @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 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() { - @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; - } + 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() { + @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 items) throws IOException { - if (items == null) return; - final ObjectCursor.ValuesCreator baseItemCreator = - ObjectCursor.valuesCreatorFrom(FiltersData.BaseItem.class); - List values = new ArrayList<>(items.size()); - for (FiltersData.BaseItem item : items) { - values.add(baseItemCreator.create(item)); + void insertBase(ContentResolver cr, Uri uri, List items) throws IOException { + if (items == null) return; + final ObjectCursor.ValuesCreator baseItemCreator = + ObjectCursor.valuesCreatorFrom(FiltersData.BaseItem.class); + List values = new ArrayList<>(items.size()); + for (FiltersData.BaseItem item : items) { + values.add(baseItemCreator.create(item)); + } + ContentResolverUtils.bulkInsert(cr, uri, values); } - ContentResolverUtils.bulkInsert(cr, uri, values); - } - void insertUser(ContentResolver cr, Uri uri, List items) throws IOException { - if (items == null) return; - final ObjectCursor.ValuesCreator userItemCreator = - ObjectCursor.valuesCreatorFrom(FiltersData.UserItem.class); + void insertUser(ContentResolver cr, Uri uri, List items) throws IOException { + if (items == null) return; + final ObjectCursor.ValuesCreator userItemCreator = + ObjectCursor.valuesCreatorFrom(FiltersData.UserItem.class); + List 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 creator = ObjectCursor.valuesCreatorFrom(Tab.class); + importItemsList(context, entry, ENTRY_TABS, Tab.class, data, (cr, items) -> { + if (items == null) return false; List values = new ArrayList<>(items.size()); - for (FiltersData.UserItem item : items) { - values.add(userItemCreator.create(item)); + for (Tab item : items) { + values.add(creator.create(item)); } - ContentResolverUtils.bulkInsert(cr, uri, values); - } - }); - } - if (hasFlag(flags, FLAG_TABS)) { - final ObjectCursor.ValuesCreator creator = ObjectCursor.valuesCreatorFrom(Tab.class); - importItemsList(context, zipFile, ENTRY_TABS, Tab.class, (cr, items) -> { - if (items == null) return false; - List 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; - }); + cr.delete(Tabs.CONTENT_URI, null, null); + ContentResolverUtils.bulkInsert(cr, Tabs.CONTENT_URI, values); + return true; + }); + } } } } @@ -267,12 +288,12 @@ private static boolean hasFlag(final int flags, final int flag) { 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 @@ private static void exportSharedPreferencesData(@NonNull final ZipOutputStream z } private static void importItemsList(@NonNull final Context context, - @NonNull final ZipFile zipFile, + @NonNull final ZipEntry entry, @NonNull final String entryName, @NonNull final Class itemCls, + @NonNull final String data, @NonNull final ContentResolverProcessStrategy> strategy) throws IOException { - final ZipEntry entry = zipFile.getEntry(entryName); - if (entry == null) return; - List itemsList = JsonSerializer.parseList(zipFile.getInputStream(entry), itemCls); + if (!Objects.equals(entry.getName(), entryName)) return; + List itemsList = JsonSerializer.parseList(data, itemCls); strategy.importItem(context.getContentResolver(), itemsList); } @@ -317,19 +338,22 @@ private static void exportItemsList(@NonNull final ZipOutputStream zos, @NonNull final Class itemCls, @NonNull final List 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 void importItem(@NonNull final Context context, - @NonNull final ZipFile zipFile, + @NonNull final ZipEntry entry, @NonNull final String entryName, @NonNull final Class itemCls, + @NonNull final String data, @NonNull final ContentResolverProcessStrategy 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 @@ private static void exportItem(@NonNull final ZipOutputStream zos, @NonNull final Class 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(); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java b/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java index 3446d8e0fe..d3004522a4 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java @@ -111,7 +111,7 @@ public static String getApiBaseUrl(@NonNull String format, @Nullable final Strin } 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) { diff --git a/twidere/src/main/kotlin/androidx/core/os/LocaleHelperAccessor.kt b/twidere/src/main/kotlin/androidx/core/os/LocaleHelperAccessor.kt index c830579e9f..5a1e268c91 100644 --- a/twidere/src/main/kotlin/androidx/core/os/LocaleHelperAccessor.kt +++ b/twidere/src/main/kotlin/androidx/core/os/LocaleHelperAccessor.kt @@ -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]") } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt index 6e19b828cf..3af8d91651 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataExportActivity.kt @@ -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() { 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 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt index 97623cacdd..7d2826064c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/DataImportActivity.kt @@ -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() { 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() { + internal class OpenImportTypeTask(private val activity: DataImportActivity, private val file: DocumentFile?) : AsyncTask() { 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) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/FileSelectorActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/FileSelectorActivity.kt index 85a84df2da..b808436fde 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/FileSelectorActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/FileSelectorActivity.kt @@ -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, - 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() } + 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() { - 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) + 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") } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt index 4007130fb2..0739981377 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt @@ -40,6 +40,9 @@ val AccountExtras.official: Boolean return false } +val AccountDetails.hasDm: Boolean + get() = type in arrayOf(AccountType.FANFOU, AccountType.TWITTER) + fun AccountDetails.newMicroBlogInstance(context: Context, cls: Class): T { return credentials.newMicroBlogInstance(context, type, cls) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/search/SearchFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/search/SearchFragment.kt index 7d38f60897..929ca1674c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/search/SearchFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/search/SearchFragment.kt @@ -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) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt index f1f5b1c7b2..b935361f3c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt @@ -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() } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt index b294b2b425..24533843ff 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt @@ -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 } diff --git a/twidere/src/main/res/values/themes_base_dark.xml b/twidere/src/main/res/values/themes_base_dark.xml index 8c7f1909ef..877fad2f08 100644 --- a/twidere/src/main/res/values/themes_base_dark.xml +++ b/twidere/src/main/res/values/themes_base_dark.xml @@ -19,7 +19,7 @@ -