Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: export workbook #116

Merged
merged 1 commit into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 89 additions & 54 deletions android/app/src/main/kotlin/org/eu/mumulhl/ciyue/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import android.os.Bundle
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.callback.SingleFileConflictCallback
import com.anggrayudi.storage.callback.SingleFolderConflictCallback
import com.anggrayudi.storage.result.SingleFolderResult
import com.anggrayudi.storage.file.DocumentFileCompat
import com.anggrayudi.storage.file.copyFolderTo
import io.flutter.embedding.android.FlutterActivity
Expand All @@ -17,87 +16,123 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File

class MainActivity : FlutterActivity() {
private val CHANNEL = "org.eu.mumulhl.ciyue"
private var methodChannel: MethodChannel? = null
private val REQUEST_CODE_OPEN_DOCUMENT_TREE = 0


private val OPEN_DOCUMENT_TREE = 0
private val CREATE_FILE = 1

private val job = Job()
private val ioScope = CoroutineScope(Dispatchers.IO + job)
private val uiScope = CoroutineScope(Dispatchers.IO + job)

var exportContent = ""

private fun openDirectory() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, OPEN_DOCUMENT_TREE)
}

private fun createFile() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
setType("application/json")
putExtra(Intent.EXTRA_TITLE, "ciyue.json")
}
startActivityForResult(intent, CREATE_FILE)
}

override fun onActivityResult(
requestCode: Int, resultCode: Int, resultData: Intent?
requestCode: Int, resultCode: Int, data: Intent?
) {
super.onActivityResult(requestCode, resultCode, resultData)

if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION
applicationContext.contentResolver.takePersistableUriPermission(uri, takeFlags)

val documents = DocumentFile.fromTreeUri(applicationContext, uri)!!

val cacheDir = File(applicationContext.cacheDir, "dictionaries_cache")
if (!cacheDir.exists()) {
cacheDir.mkdir()
super.onActivityResult(requestCode, resultCode, data)

if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
OPEN_DOCUMENT_TREE -> openDocumentTree(data)
CREATE_FILE -> {
data?.data?.also { uri ->
contentResolver.openOutputStream(uri)?.use { outputStream ->
outputStream.write(exportContent.toByteArray())
exportContent = ""
}
}
}
val targetFolder = DocumentFileCompat.fromFile(applicationContext, cacheDir)!!

ioScope.launch {
documents.copyFolderTo(
applicationContext,
targetFolder,
onConflict = object : SingleFolderConflictCallback(uiScope) {
override fun onParentConflict(
destinationFolder: DocumentFile,
action: ParentFolderConflictAction,
canMerge: Boolean
) {
action.confirmResolution(ConflictResolution.SKIP)
}
}
}
}

private fun openDocumentTree(data: Intent?) {
data?.data?.also { uri ->
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION
applicationContext.contentResolver.takePersistableUriPermission(
uri, takeFlags
)

override fun onContentConflict(
destinationFolder: DocumentFile,
conflictedFiles: MutableList<FileConflict>,
action: FolderContentConflictAction
) {
val newSolution = ArrayList<FileConflict>(conflictedFiles.size)
conflictedFiles.forEach {
it.solution = SingleFileConflictCallback.ConflictResolution.SKIP
}
newSolution.addAll(conflictedFiles)
action.confirmResolution(newSolution)
val documents = DocumentFile.fromTreeUri(applicationContext, uri)!!

val cacheDir = File(applicationContext.cacheDir, "dictionaries_cache")
if (!cacheDir.exists()) {
cacheDir.mkdir()
}
val targetFolder =
DocumentFileCompat.fromFile(applicationContext, cacheDir)!!

ioScope.launch {
documents.copyFolderTo(applicationContext,
targetFolder,
onConflict = object : SingleFolderConflictCallback(uiScope) {
override fun onParentConflict(
destinationFolder: DocumentFile,
action: ParentFolderConflictAction,
canMerge: Boolean
) {
action.confirmResolution(ConflictResolution.SKIP)
}

override fun onContentConflict(
destinationFolder: DocumentFile,
conflictedFiles: MutableList<FileConflict>,
action: FolderContentConflictAction
) {
val newSolution =
ArrayList<FileConflict>(conflictedFiles.size)
conflictedFiles.forEach {
it.solution =
SingleFileConflictCallback.ConflictResolution.SKIP
}
}).onCompletion {
}.collect { _ ->
}
newSolution.addAll(conflictedFiles)
action.confirmResolution(newSolution)

}
methodChannel?.invokeMethod("inputDirectory", null)
}
}).onCompletion {}.collect { _ -> }
}
methodChannel!!.invokeMethod("inputDirectory", null)
}
}

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
methodChannel!!.setMethodCallHandler { call, result ->
if (call.method == "openDirectory") {
openDirectory()
result.success(0)
} else {
result.notImplemented()
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).apply {
setMethodCallHandler { call, result ->
when (call.method) {
"openDirectory" -> {
openDirectory()
result.success(0)
}

"createFile" -> {
exportContent = call.arguments as String
createFile()
result.success(0)
}

else -> result.notImplemented()
}
}
}
}
Expand Down
22 changes: 2 additions & 20 deletions lib/pages/main/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_gen/gen_l10n/app_localizations.dart";
import "package:go_router/go_router.dart";
import "package:path/path.dart";
import "package:url_launcher/url_launcher.dart";

const feedbackUri = "https://github.com/mumu-lhl/Ciyue/issues";
Expand Down Expand Up @@ -86,37 +85,20 @@ class ClearHistory extends StatelessWidget {
}

class Export extends StatelessWidget {
const Export({
super.key,
});
const Export({super.key});

@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(Icons.file_upload),
title: Text(AppLocalizations.of(context)!.export),
onTap: () async {
final directoryPath = await getDirectoryPath();
if (directoryPath == null || dictManager.isEmpty) {
return;
}

final dictionaryName = basename(dictManager.dicts.values.first.path),
filename = setExtension(dictionaryName, ".json"),
saveLocation = join(directoryPath, filename);

if (settings.autoExport) {
prefs.setString("autoExportPath", saveLocation);
}

final words = await wordbookDao.getAllWords(),
tags = await wordbookTagsDao.getAllTags();

if (words.isNotEmpty) {
final wordsOutput = jsonEncode(words), tagsOutput = jsonEncode(tags);

final file = File(saveLocation);
await file.writeAsString("$wordsOutput\n$tagsOutput");
platform.invokeMethod("createFile", "$wordsOutput\n$tagsOutput");
}
},
);
Expand Down
Loading