Add export config to clipboard

This commit is contained in:
problematicconsumer
2023-11-12 12:52:54 +03:30
parent 0c2e0f4070
commit d3cab28dee
15 changed files with 167 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ package com.hiddify.hiddify
import android.util.Log
import com.hiddify.hiddify.bg.BoxService
import com.hiddify.hiddify.constant.Alert
import com.hiddify.hiddify.constant.Status
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
@@ -24,6 +25,7 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
enum class Trigger(val method: String) {
ParseConfig("parse_config"),
ChangeConfigOptions("change_config_options"),
GenerateConfig("generate_config"),
Start("start"),
Stop("stop"),
Restart("restart"),
@@ -70,6 +72,21 @@ class MethodHandler(private val scope: CoroutineScope) : FlutterPlugin,
}
}
Trigger.GenerateConfig.method -> {
scope.launch {
result.runCatching {
val args = call.arguments as Map<*, *>
val path = args["path"] as String
val options = Settings.configOptions
if (options.isBlank() || path.isBlank()) {
error("blank properties")
}
val config = BoxService.buildConfig(path, options)
success(config)
}
}
}
Trigger.Start.method -> {
scope.launch {
result.runCatching {

View File

@@ -72,6 +72,10 @@ class BoxService(
}
}
fun buildConfig(path: String, options: String):String {
return Mobile.buildConfig(path, options)
}
fun start() {
val intent = runBlocking {
withContext(Dispatchers.IO) {

View File

@@ -67,6 +67,11 @@
"failureMsg": "Failed to update profile",
"successMsg": "Profile updated successfully"
},
"share": {
"buttonText": "Share",
"exportConfigToClipboard": "Export configuration to clipboard",
"exportConfigToClipboardSuccess": "Configuration copied to clipboard"
},
"edit": {
"buttonTxt": "Edit",
"selectActiveTxt": "Select active profile"

View File

@@ -67,6 +67,11 @@
"failureMsg": "در بروزرسانی پروفایل خطایی رخ داد",
"successMsg": "پروفایل با موفقیت بروزرسانی شد"
},
"share": {
"buttonText": "اشتراک گذاری",
"exportConfigToClipboard": "افزودن پیکربندی به کلیپ بورد",
"exportConfigToClipboardSuccess": "پیکربندی در کلیپ بورد کپی شد"
},
"edit": {
"buttonTxt": "ویرایش",
"selectActiveTxt": "انتخاب پروفایل فعال"

View File

@@ -67,6 +67,11 @@
"failureMsg": "Ошибка обновления",
"successMsg": "Профиль успешно обновлён"
},
"share": {
"buttonText": "Делиться",
"exportConfigToClipboard": "Экспортировать конфигурацию в буфер обмена",
"exportConfigToClipboardSuccess": "Конфигурация скопирована в буфер обмена."
},
"edit": {
"buttonTxt": "Изменить",
"selectActiveTxt": "Выберите активный профиль"

View File

@@ -67,6 +67,11 @@
"failureMsg": "更新配置文件失败",
"successMsg": "配置文件更新成功"
},
"share": {
"buttonText": "分享",
"exportConfigToClipboard": "将配置导出到剪贴板",
"exportConfigToClipboardSuccess": "配置已复制到剪贴板"
},
"edit": {
"buttonTxt": "编辑",
"selectActiveTxt": "选择活动配置文件"

View File

@@ -90,6 +90,27 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade {
);
}
@override
TaskEither<CoreServiceFailure, String> generateConfig(
String fileName,
) {
return exceptionHandler(
() {
final configPath = filesEditor.configPath(fileName);
final options = configOptions();
return setup()
.andThen(() => changeConfigOptions(options))
.andThen(
() => singbox
.generateConfig(configPath)
.mapLeft(CoreServiceFailure.other),
)
.run();
},
CoreServiceFailure.unexpected,
);
}
@override
TaskEither<CoreServiceFailure, Unit> start(
String fileName,

View File

@@ -18,6 +18,10 @@ abstract interface class SingboxFacade {
ConfigOptions options,
);
TaskEither<CoreServiceFailure, String> generateConfig(
String fileName,
);
TaskEither<CoreServiceFailure, Unit> start(
String fileName,
bool disableMemoryLimit,

View File

@@ -254,6 +254,14 @@ class ProfileActionsMenu extends HookConsumerWidget {
initialOnSuccess: () =>
CustomToast.success(t.profile.update.successMsg).show(context),
);
final exportConfigMutation = useMutation(
initialOnFailure: (err) {
CustomToast.error(t.presentShortError(err)).show(context);
},
initialOnSuccess: () =>
CustomToast.success(t.profile.share.exportConfigToClipboardSuccess)
.show(context),
);
final deleteProfileMutation = useMutation(
initialOnFailure: (err) {
CustomAlertDialog.fromErr(t.presentError(err)).show(context);
@@ -278,6 +286,25 @@ class ProfileActionsMenu extends HookConsumerWidget {
);
},
),
SubmenuButton(
menuChildren: [
MenuItemButton(
child: Text(t.profile.share.exportConfigToClipboard),
onPressed: () async {
if (exportConfigMutation.state.isInProgress) {
return;
}
exportConfigMutation.setFuture(
ref
.read(profilesNotifierProvider.notifier)
.exportConfigToClipboard(profile),
);
},
),
],
leadingIcon: const Icon(Icons.share),
child: Text(t.profile.share.buttonText),
),
MenuItemButton(
leadingIcon: const Icon(Icons.edit),
child: Text(t.profile.edit.buttonTxt),

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:fpdart/fpdart.dart';
import 'package:hiddify/core/prefs/prefs.dart';
import 'package:hiddify/data/data_providers.dart';
@@ -124,4 +125,16 @@ class ProfilesNotifier extends _$ProfilesNotifier with AppLogger {
},
).run();
}
Future<void> exportConfigToClipboard(Profile profile) async {
await ref.read(coreFacadeProvider).generateConfig(profile.id).match(
(err) {
loggy.warning('error generating config', err);
throw err;
},
(configJson) async {
await Clipboard.setData(ClipboardData(text: configJson));
},
).run();
}
}

View File

@@ -934,6 +934,21 @@ class SingboxNativeLibrary {
late final _changeConfigOptions = _changeConfigOptionsPtr
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> generateConfig(
ffi.Pointer<ffi.Char> path,
) {
return _generateConfig(
path,
);
}
late final _generateConfigPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<ffi.Char> Function(
ffi.Pointer<ffi.Char>)>>('generateConfig');
late final _generateConfig = _generateConfigPtr
.asFunction<ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Char>)>();
ffi.Pointer<ffi.Char> start(
ffi.Pointer<ffi.Char> configPath,
int disableMemoryLimit,

View File

@@ -5,6 +5,7 @@ import 'dart:io';
import 'dart:isolate';
import 'package:combine/combine.dart';
import 'package:dartx/dartx.dart';
import 'package:ffi/ffi.dart';
import 'package:fpdart/fpdart.dart';
import 'package:hiddify/domain/connectivity/connectivity.dart';
@@ -137,6 +138,28 @@ class FFISingboxService
);
}
@override
TaskEither<String, String> generateConfig(
String path,
) {
return TaskEither(
() => CombineWorker().execute(
() {
final response = _box
.generateConfig(
path.toNativeUtf8().cast(),
)
.cast<Utf8>()
.toDartString();
if (response.startsWith("error")) {
return left(response.removePrefix("error"));
}
return right(response);
},
),
);
}
@override
TaskEither<String, Unit> start(String configPath, bool disableMemoryLimit) {
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");

View File

@@ -73,6 +73,24 @@ class MobileSingboxService
);
}
@override
TaskEither<String, String> generateConfig(
String path,
) {
return TaskEither(
() async {
final configJson = await _methodChannel.invokeMethod<String>(
"generate_config",
{"path": path},
);
if (configJson == null || configJson.isEmpty) {
return left("null response");
}
return right(configJson);
},
);
}
@override
TaskEither<String, Unit> start(String configPath, bool disableMemoryLimit) {
return TaskEither(

View File

@@ -33,6 +33,10 @@ abstract interface class SingboxService {
TaskEither<String, Unit> changeConfigOptions(ConfigOptions options);
TaskEither<String, String> generateConfig(
String path,
);
TaskEither<String, Unit> start(String configPath, bool disableMemoryLimit);
TaskEither<String, Unit> stop();

Submodule libcore updated: 5c8b283d9c...953e6a02d7