fix: replace open_file_plus with native Kotlin MethodChannel for APK install
open_file_plus 3.4.1 uses deprecated PluginRegistry.Registrar API incompatible with current Flutter. Replaced with a MethodChannel in MainActivity.kt that calls FileProvider.getUriForFile() and launches the system package installer directly. Also rework chat_input: 4px border radius on desktop, vPad 18px, 56px send button. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,51 @@
|
|||||||
package chat.pyramid.pyramid
|
package chat.pyramid.pyramid
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
class MainActivity : FlutterActivity() {
|
||||||
|
private val channel = "chat.pyramid.pyramid/install"
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel)
|
||||||
|
.setMethodCallHandler { call, result ->
|
||||||
|
if (call.method == "installApk") {
|
||||||
|
val path = call.argument<String>("path")
|
||||||
|
if (path == null) {
|
||||||
|
result.error("INVALID_ARG", "path is null", null)
|
||||||
|
return@setMethodCallHandler
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val file = File(path)
|
||||||
|
val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
FileProvider.getUriForFile(
|
||||||
|
this,
|
||||||
|
"${applicationContext.packageName}.fileprovider",
|
||||||
|
file
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Uri.fromFile(file)
|
||||||
|
}
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
setDataAndType(uri, "application/vnd.android.package-archive")
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
result.success(null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("INSTALL_ERROR", e.message, null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,11 +43,7 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
if (text.isEmpty) return;
|
if (text.isEmpty) return;
|
||||||
_ctrl.clear();
|
_ctrl.clear();
|
||||||
setState(() => _canSend = false);
|
setState(() => _canSend = false);
|
||||||
|
await widget.room.sendTextEvent(text, inReplyTo: widget.replyTo);
|
||||||
await widget.room.sendTextEvent(
|
|
||||||
text,
|
|
||||||
inReplyTo: widget.replyTo,
|
|
||||||
);
|
|
||||||
widget.onClearReply?.call();
|
widget.onClearReply?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,21 +55,30 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
defaultTargetPlatform == TargetPlatform.linux ||
|
defaultTargetPlatform == TargetPlatform.linux ||
|
||||||
defaultTargetPlatform == TargetPlatform.macOS);
|
defaultTargetPlatform == TargetPlatform.macOS);
|
||||||
|
|
||||||
|
// Desktop: taller bar, 4px square corners, bigger button
|
||||||
|
// Mobile: compact bar, pill shape, smaller button
|
||||||
|
final fieldRadius = isDesktop ? 4.0 : 20.0;
|
||||||
|
final btnSize = isDesktop ? 56.0 : 44.0;
|
||||||
|
final btnRadius = isDesktop ? 6.0 : 12.0;
|
||||||
|
final iconSize = isDesktop ? 24.0 : 20.0;
|
||||||
|
final fontSize = isDesktop ? 15.0 : 14.0;
|
||||||
|
final vPad = isDesktop ? 18.0 : 10.0;
|
||||||
|
final hPad = isDesktop ? 16.0 : 14.0;
|
||||||
|
final outerPad = isDesktop
|
||||||
|
? const EdgeInsets.fromLTRB(12, 10, 12, 12)
|
||||||
|
: const EdgeInsets.fromLTRB(8, 4, 8, 8);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (widget.replyTo != null) _ReplyBar(
|
if (widget.replyTo != null)
|
||||||
event: widget.replyTo!,
|
_ReplyBar(
|
||||||
onClear: widget.onClearReply ?? () {},
|
event: widget.replyTo!,
|
||||||
),
|
onClear: widget.onClearReply ?? () {},
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
isDesktop ? 12 : 8,
|
|
||||||
isDesktop ? 10 : 4,
|
|
||||||
isDesktop ? 12 : 8,
|
|
||||||
isDesktop ? 14 : 8,
|
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: outerPad,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
@@ -83,19 +88,24 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 6,
|
maxLines: 6,
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
style: TextStyle(fontSize: isDesktop ? 16 : 14),
|
style: TextStyle(fontSize: fontSize),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Nachricht...',
|
hintText: 'Nachricht...',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(fontSize: fontSize),
|
||||||
fontSize: isDesktop ? 16 : 14,
|
|
||||||
color: theme.colorScheme.onSurface.withAlpha(100),
|
|
||||||
),
|
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: isDesktop ? 22 : 16,
|
horizontal: hPad,
|
||||||
vertical: isDesktop ? 20 : 10,
|
vertical: vPad,
|
||||||
),
|
),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(isDesktop ? 16 : 24),
|
borderRadius: BorderRadius.circular(fieldRadius),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(fieldRadius),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(fieldRadius),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
filled: true,
|
filled: true,
|
||||||
@@ -113,18 +123,18 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
onTap: _canSend ? _send : null,
|
onTap: _canSend ? _send : null,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
width: isDesktop ? 60 : 44,
|
width: btnSize,
|
||||||
height: isDesktop ? 60 : 44,
|
height: btnSize,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _canSend
|
color: _canSend
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.primary
|
||||||
: theme.colorScheme.primary.withAlpha(70),
|
: theme.colorScheme.primary.withAlpha(70),
|
||||||
borderRadius: BorderRadius.circular(isDesktop ? 16 : 14),
|
borderRadius: BorderRadius.circular(btnRadius),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.send_rounded,
|
Icons.send_rounded,
|
||||||
size: isDesktop ? 26 : 20,
|
size: iconSize,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:open_file_plus/open_file_plus.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:pyramid/core/theme.dart';
|
import 'package:pyramid/core/theme.dart';
|
||||||
@@ -111,27 +111,28 @@ class _UpdateDownloadDialogState extends ConsumerState<_UpdateDownloadDialog>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const _installChannel =
|
||||||
|
MethodChannel('chat.pyramid.pyramid/install');
|
||||||
|
|
||||||
Future<void> _install(File file) async {
|
Future<void> _install(File file) async {
|
||||||
setState(() => _state = _DownloadState.installing);
|
setState(() => _state = _DownloadState.installing);
|
||||||
|
|
||||||
if (Platform.isWindows) {
|
try {
|
||||||
await Process.start(file.path, [], runInShell: false);
|
if (Platform.isWindows) {
|
||||||
if (mounted) Navigator.of(context).pop();
|
await Process.start(file.path, [], runInShell: false);
|
||||||
exit(0);
|
if (mounted) Navigator.of(context).pop();
|
||||||
} else if (Platform.isAndroid) {
|
exit(0);
|
||||||
final result = await OpenFile.open(
|
} else if (Platform.isAndroid) {
|
||||||
file.path,
|
await _installChannel
|
||||||
type: 'application/vnd.android.package-archive',
|
.invokeMethod('installApk', {'path': file.path});
|
||||||
);
|
if (mounted) setState(() => _state = _DownloadState.done);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (result.type == ResultType.done) {
|
setState(() {
|
||||||
setState(() => _state = _DownloadState.done);
|
_state = _DownloadState.error;
|
||||||
} else {
|
_errorMsg = e.toString().split('\n').first;
|
||||||
setState(() {
|
});
|
||||||
_state = _DownloadState.error;
|
|
||||||
_errorMsg = result.message;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -757,14 +757,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.3.0"
|
version: "9.3.0"
|
||||||
open_file_plus:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: open_file_plus
|
|
||||||
sha256: "01ad7011d46d253aa22e5f0bc7345ca49cfe652c50ae9b0af945491c1728a8c3"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.4.1+1"
|
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ dependencies:
|
|||||||
# File handling
|
# File handling
|
||||||
file_picker: ^8.1.7
|
file_picker: ^8.1.7
|
||||||
desktop_drop: ^0.4.4
|
desktop_drop: ^0.4.4
|
||||||
open_file_plus: ^3.4.1
|
|
||||||
|
|
||||||
# Media playback
|
# Media playback
|
||||||
media_kit: ^1.1.11
|
media_kit: ^1.1.11
|
||||||
|
|||||||
Reference in New Issue
Block a user