diff --git a/android/app/src/main/kotlin/chat/pyramid/pyramid/MainActivity.kt b/android/app/src/main/kotlin/chat/pyramid/pyramid/MainActivity.kt index 3ac10da..6310bfa 100644 --- a/android/app/src/main/kotlin/chat/pyramid/pyramid/MainActivity.kt +++ b/android/app/src/main/kotlin/chat/pyramid/pyramid/MainActivity.kt @@ -1,5 +1,51 @@ 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.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("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() + } + } + } +} diff --git a/lib/features/chat/chat_input.dart b/lib/features/chat/chat_input.dart index f5230df..e88b3af 100644 --- a/lib/features/chat/chat_input.dart +++ b/lib/features/chat/chat_input.dart @@ -43,11 +43,7 @@ class _ChatInputState extends State { if (text.isEmpty) return; _ctrl.clear(); setState(() => _canSend = false); - - await widget.room.sendTextEvent( - text, - inReplyTo: widget.replyTo, - ); + await widget.room.sendTextEvent(text, inReplyTo: widget.replyTo); widget.onClearReply?.call(); } @@ -59,21 +55,30 @@ class _ChatInputState extends State { defaultTargetPlatform == TargetPlatform.linux || 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( child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (widget.replyTo != null) _ReplyBar( - event: widget.replyTo!, - onClear: widget.onClearReply ?? () {}, - ), - Padding( - padding: EdgeInsets.fromLTRB( - isDesktop ? 12 : 8, - isDesktop ? 10 : 4, - isDesktop ? 12 : 8, - isDesktop ? 14 : 8, + if (widget.replyTo != null) + _ReplyBar( + event: widget.replyTo!, + onClear: widget.onClearReply ?? () {}, ), + Padding( + padding: outerPad, child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ @@ -83,19 +88,24 @@ class _ChatInputState extends State { minLines: 1, maxLines: 6, textCapitalization: TextCapitalization.sentences, - style: TextStyle(fontSize: isDesktop ? 16 : 14), + style: TextStyle(fontSize: fontSize), decoration: InputDecoration( hintText: 'Nachricht...', - hintStyle: TextStyle( - fontSize: isDesktop ? 16 : 14, - color: theme.colorScheme.onSurface.withAlpha(100), - ), + hintStyle: TextStyle(fontSize: fontSize), contentPadding: EdgeInsets.symmetric( - horizontal: isDesktop ? 22 : 16, - vertical: isDesktop ? 20 : 10, + horizontal: hPad, + vertical: vPad, ), 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, ), filled: true, @@ -113,18 +123,18 @@ class _ChatInputState extends State { onTap: _canSend ? _send : null, child: AnimatedContainer( duration: const Duration(milliseconds: 150), - width: isDesktop ? 60 : 44, - height: isDesktop ? 60 : 44, + width: btnSize, + height: btnSize, decoration: BoxDecoration( color: _canSend ? theme.colorScheme.primary : theme.colorScheme.primary.withAlpha(70), - borderRadius: BorderRadius.circular(isDesktop ? 16 : 14), + borderRadius: BorderRadius.circular(btnRadius), ), child: Center( child: Icon( Icons.send_rounded, - size: isDesktop ? 26 : 20, + size: iconSize, color: Colors.white, ), ), diff --git a/lib/widgets/update_download_dialog.dart b/lib/widgets/update_download_dialog.dart index 4a4a384..c04fe93 100644 --- a/lib/widgets/update_download_dialog.dart +++ b/lib/widgets/update_download_dialog.dart @@ -1,9 +1,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; 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_provider/path_provider.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 _install(File file) async { setState(() => _state = _DownloadState.installing); - if (Platform.isWindows) { - await Process.start(file.path, [], runInShell: false); - if (mounted) Navigator.of(context).pop(); - exit(0); - } else if (Platform.isAndroid) { - final result = await OpenFile.open( - file.path, - type: 'application/vnd.android.package-archive', - ); + try { + if (Platform.isWindows) { + await Process.start(file.path, [], runInShell: false); + if (mounted) Navigator.of(context).pop(); + exit(0); + } else if (Platform.isAndroid) { + await _installChannel + .invokeMethod('installApk', {'path': file.path}); + if (mounted) setState(() => _state = _DownloadState.done); + } + } catch (e) { if (mounted) { - if (result.type == ResultType.done) { - setState(() => _state = _DownloadState.done); - } else { - setState(() { - _state = _DownloadState.error; - _errorMsg = result.message; - }); - } + setState(() { + _state = _DownloadState.error; + _errorMsg = e.toString().split('\n').first; + }); } } } diff --git a/pubspec.lock b/pubspec.lock index 0d677d6..27beb78 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -757,14 +757,6 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b29aae1..d366c1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,7 +57,6 @@ dependencies: # File handling file_picker: ^8.1.7 desktop_drop: ^0.4.4 - open_file_plus: ^3.4.1 # Media playback media_kit: ^1.1.11