Compare commits
	
		
			7 Commits
		
	
	
		
			3.1.0+122
			...
			c061ef2132
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c061ef2132 | |||
| c378309bdd | |||
| b2c5d64fc5 | |||
|  | 5371637b16 | ||
| c5cbf0af37 | |||
| 1a31e22450 | |||
|  | 49db54529d | 
| @@ -51,6 +51,12 @@ android { | |||||||
|     buildTypes { |     buildTypes { | ||||||
|         release { |         release { | ||||||
|             signingConfig = signingConfigs.getByName("release") |             signingConfig = signingConfigs.getByName("release") | ||||||
|  |  | ||||||
|  |             isMinifyEnabled = true | ||||||
|  |             proguardFiles( | ||||||
|  |                 getDefaultProguardFile("proguard-android-optimize.txt"), | ||||||
|  |                 "proguard-rules.pro" | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -58,7 +64,7 @@ android { | |||||||
| dependencies { | dependencies { | ||||||
|     implementation("com.google.android.material:material:1.12.0") |     implementation("com.google.android.material:material:1.12.0") | ||||||
|     implementation("com.github.bumptech.glide:glide:4.16.0") |     implementation("com.github.bumptech.glide:glide:4.16.0") | ||||||
|     implementation("com.squareup.okhttp3:okhttp:4.12.0") |     implementation("com.squareup.okhttp3:okhttp:5.1.0") | ||||||
| } | } | ||||||
|  |  | ||||||
| flutter { | flutter { | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								android/app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | # JNI Zero initialization (required for WebRTC native method registration) | ||||||
|  | -keep class livekit.org.jni_zero.JniInit { | ||||||
|  |     # Keep the init method un-obfuscated for native code callback | ||||||
|  |     private static java.lang.Object[] init(); | ||||||
|  | } | ||||||
| @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME | |||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip | ||||||
|   | |||||||
| @@ -18,11 +18,11 @@ pluginManagement { | |||||||
|  |  | ||||||
| plugins { | plugins { | ||||||
|     id("dev.flutter.flutter-plugin-loader") version "1.0.0" |     id("dev.flutter.flutter-plugin-loader") version "1.0.0" | ||||||
|     id("com.android.application") version "8.10.1" apply false |     id("com.android.application") version "8.12.0" apply false | ||||||
|     // START: FlutterFire Configuration |     // START: FlutterFire Configuration | ||||||
|     id("com.google.gms.google-services") version("4.3.15") apply false |     id("com.google.gms.google-services") version("4.3.15") apply false | ||||||
|     // END: FlutterFire Configuration |     // END: FlutterFire Configuration | ||||||
|     id("org.jetbrains.kotlin.android") version "1.8.22" apply false |     id("org.jetbrains.kotlin.android") version("2.2.0") apply false | ||||||
| } | } | ||||||
|  |  | ||||||
| include(":app") | include(":app") | ||||||
|   | |||||||
| @@ -789,5 +789,6 @@ | |||||||
|   "linkKey": "Link Name", |   "linkKey": "Link Name", | ||||||
|   "linkValue": "URL", |   "linkValue": "URL", | ||||||
|   "debugOptions": "Debug Options", |   "debugOptions": "Debug Options", | ||||||
|   "joinedAt": "Joined at {}" |   "joinedAt": "Joined at {}", | ||||||
|  |   "searchAccounts": "Search accounts..." | ||||||
| } | } | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -28,6 +28,7 @@ import 'package:relative_time/relative_time.dart'; | |||||||
| import 'package:shared_preferences/shared_preferences.dart'; | import 'package:shared_preferences/shared_preferences.dart'; | ||||||
| import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; | ||||||
| import 'package:flutter_native_splash/flutter_native_splash.dart'; | import 'package:flutter_native_splash/flutter_native_splash.dart'; | ||||||
|  | import 'package:island/widgets/keyboard_navigation.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
| import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; | import 'package:flutter_langdetect/flutter_langdetect.dart' as langdetect; | ||||||
|  |  | ||||||
| @@ -244,30 +245,32 @@ class IslandApp extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final router = ref.watch(routerProvider); |     final router = ref.watch(routerProvider); | ||||||
|  |  | ||||||
|     return MaterialApp.router( |     return KeyboardNavigation( | ||||||
|       theme: theme?.light, |       child: MaterialApp.router( | ||||||
|       darkTheme: theme?.dark, |         theme: theme?.light, | ||||||
|       themeMode: ThemeMode.system, |         darkTheme: theme?.dark, | ||||||
|       routerConfig: router, |         themeMode: ThemeMode.system, | ||||||
|       supportedLocales: context.supportedLocales, |         routerConfig: router, | ||||||
|       localizationsDelegates: [ |         supportedLocales: context.supportedLocales, | ||||||
|         ...context.localizationDelegates, |         localizationsDelegates: [ | ||||||
|         CroppyLocalizations.delegate, |           ...context.localizationDelegates, | ||||||
|         RelativeTimeLocalizations.delegate, |           CroppyLocalizations.delegate, | ||||||
|       ], |           RelativeTimeLocalizations.delegate, | ||||||
|       locale: context.locale, |         ], | ||||||
|       builder: (context, child) { |         locale: context.locale, | ||||||
|         return Overlay( |         builder: (context, child) { | ||||||
|           key: globalOverlay, |           return Overlay( | ||||||
|           initialEntries: [ |             key: globalOverlay, | ||||||
|             OverlayEntry( |             initialEntries: [ | ||||||
|               builder: |               OverlayEntry( | ||||||
|                   (_) => |                 builder: | ||||||
|                       WindowScaffold(child: child ?? const SizedBox.shrink()), |                     (_) => | ||||||
|             ), |                         WindowScaffold(child: child ?? const SizedBox.shrink()), | ||||||
|           ], |               ), | ||||||
|         ); |             ], | ||||||
|       }, |           ); | ||||||
|  |         }, | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -216,6 +216,7 @@ class RelationshipScreen extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => AccountPickerSheet(), |         builder: (context) => AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
| @@ -227,6 +227,7 @@ class ChatListScreen extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
|   | |||||||
| @@ -589,6 +589,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         context: context, |         context: context, | ||||||
|         useRootNavigator: true, |         useRootNavigator: true, | ||||||
|  |         isScrollControlled: true, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|       if (result == null) return; |       if (result == null) return; | ||||||
| @@ -727,7 +728,7 @@ class _ChatMemberListSheet extends HookConsumerWidget { | |||||||
|                                       apiClientProvider, |                                       apiClientProvider, | ||||||
|                                     ); |                                     ); | ||||||
|                                     await apiClient.delete( |                                     await apiClient.delete( | ||||||
|                                       '/chat/$roomId/members/${member.accountId}', |                                       '/sphere/chat/$roomId/members/${member.accountId}', | ||||||
|                                     ); |                                     ); | ||||||
|                                     // Refresh both providers |                                     // Refresh both providers | ||||||
|                                     memberNotifier.reset(); |                                     memberNotifier.reset(); | ||||||
|   | |||||||
| @@ -708,6 +708,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|  |         useRootNavigator: true, | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|         context: context, |         context: context, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|   | |||||||
| @@ -180,6 +180,7 @@ class StickerPackDetailScreen extends HookConsumerWidget { | |||||||
|                                               .pushNamed( |                                               .pushNamed( | ||||||
|                                                 'creatorStickerEdit', |                                                 'creatorStickerEdit', | ||||||
|                                                 pathParameters: { |                                                 pathParameters: { | ||||||
|  |                                                   'name': pubName, | ||||||
|                                                   'packId': id, |                                                   'packId': id, | ||||||
|                                                   'id': sticker.id, |                                                   'id': sticker.id, | ||||||
|                                                 }, |                                                 }, | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ class StickersScreen extends HookConsumerWidget { | |||||||
|               context |               context | ||||||
|                   .pushNamed( |                   .pushNamed( | ||||||
|                     'creatorStickerPackNew', |                     'creatorStickerPackNew', | ||||||
|                     queryParameters: {'name': pubName}, |                     pathParameters: {'name': pubName}, | ||||||
|                   ) |                   ) | ||||||
|                   .then((value) { |                   .then((value) { | ||||||
|                     if (value != null) { |                     if (value != null) { | ||||||
| @@ -187,10 +187,8 @@ class EditStickerPacksScreen extends HookConsumerWidget { | |||||||
|             'description': descriptionController.text, |             'description': descriptionController.text, | ||||||
|             'prefix': prefixController.text, |             'prefix': prefixController.text, | ||||||
|           }, |           }, | ||||||
|           options: Options( |           queryParameters: {'pub': pubName}, | ||||||
|             method: packId == null ? 'POST' : 'PATCH', |           options: Options(method: packId == null ? 'POST' : 'PATCH'), | ||||||
|             headers: {'X-Pub': pubName}, |  | ||||||
|           ), |  | ||||||
|         ); |         ); | ||||||
|         if (!context.mounted) return; |         if (!context.mounted) return; | ||||||
|         context.pop(SnStickerPack.fromJson(resp.data)); |         context.pop(SnStickerPack.fromJson(resp.data)); | ||||||
|   | |||||||
| @@ -488,6 +488,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
|       final result = await showModalBottomSheet( |       final result = await showModalBottomSheet( | ||||||
|         isScrollControlled: true, |         isScrollControlled: true, | ||||||
|  |         useRootNavigator: true, | ||||||
|         context: context, |         context: context, | ||||||
|         builder: (context) => const AccountPickerSheet(), |         builder: (context) => const AccountPickerSheet(), | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -67,6 +67,9 @@ Future<void> subscribePushNotification( | |||||||
|   Dio apiClient, { |   Dio apiClient, { | ||||||
|   bool detailedErrors = false, |   bool detailedErrors = false, | ||||||
| }) async { | }) async { | ||||||
|  |   if (Platform.isLinux){ | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|   await FirebaseMessaging.instance.requestPermission( |   await FirebaseMessaging.instance.requestPermission( | ||||||
|     alert: true, |     alert: true, | ||||||
|     badge: true, |     badge: true, | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  |  | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -44,9 +45,8 @@ class AccountPickerSheet extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Container( |     return Container( | ||||||
|       constraints: BoxConstraints( |       padding: MediaQuery.of(context).viewInsets, | ||||||
|         maxHeight: MediaQuery.of(context).size.height * 0.4, |       height: MediaQuery.of(context).size.height * 0.6, | ||||||
|       ), |  | ||||||
|       child: Column( |       child: Column( | ||||||
|         children: [ |         children: [ | ||||||
|           Padding( |           Padding( | ||||||
| @@ -54,8 +54,8 @@ class AccountPickerSheet extends HookConsumerWidget { | |||||||
|             child: TextField( |             child: TextField( | ||||||
|               controller: searchController, |               controller: searchController, | ||||||
|               onChanged: onSearchChanged, |               onChanged: onSearchChanged, | ||||||
|               decoration: const InputDecoration( |               decoration: InputDecoration( | ||||||
|                 hintText: 'Search accounts...', |                 hintText: 'searchAccounts'.tr(), | ||||||
|                 contentPadding: EdgeInsets.symmetric( |                 contentPadding: EdgeInsets.symmetric( | ||||||
|                   horizontal: 18, |                   horizontal: 18, | ||||||
|                   vertical: 16, |                   vertical: 16, | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								lib/widgets/keyboard_navigation.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/widgets/keyboard_navigation.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  |  | ||||||
|  | enum VimMode { normal, insert } | ||||||
|  |  | ||||||
|  | class KeyboardNavigation extends StatefulWidget { | ||||||
|  |   const KeyboardNavigation({super.key, required this.child}); | ||||||
|  |  | ||||||
|  |   final Widget child; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<KeyboardNavigation> createState() => _KeyboardNavigationState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _KeyboardNavigationState extends State<KeyboardNavigation> { | ||||||
|  |   VimMode _mode = VimMode.normal; | ||||||
|  |   final FocusScopeNode _focusScopeNode = FocusScopeNode(); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _focusScopeNode.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) { | ||||||
|  |     if (event is! KeyDownEvent && event is! KeyRepeatEvent) { | ||||||
|  |       return KeyEventResult.ignored; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (_mode == VimMode.normal) { | ||||||
|  |       if (event.logicalKey == LogicalKeyboardKey.keyJ) { | ||||||
|  |         node.focusInDirection(TraversalDirection.down); | ||||||
|  |         return KeyEventResult.handled; | ||||||
|  |       } else if (event.logicalKey == LogicalKeyboardKey.keyK) { | ||||||
|  |         node.focusInDirection(TraversalDirection.up); | ||||||
|  |         return KeyEventResult.handled; | ||||||
|  |       } else if (event.logicalKey == LogicalKeyboardKey.keyH) { | ||||||
|  |         final focusNode = FocusManager.instance.primaryFocus; | ||||||
|  |         if (focusNode != null) { | ||||||
|  |           final scrollable = Scrollable.of(focusNode.context!); | ||||||
|  |           if (scrollable.position.axis == Axis.horizontal) { | ||||||
|  |             scrollable.position.moveTo(scrollable.position.pixels - 50); | ||||||
|  |             return KeyEventResult.handled; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         node.focusInDirection(TraversalDirection.left); | ||||||
|  |         return KeyEventResult.handled; | ||||||
|  |       } else if (event.logicalKey == LogicalKeyboardKey.keyL) { | ||||||
|  |         final focusNode = FocusManager.instance.primaryFocus; | ||||||
|  |         if (focusNode != null) { | ||||||
|  |           final scrollable = Scrollable.of(focusNode.context!); | ||||||
|  |           if (scrollable.position.axis == Axis.horizontal) { | ||||||
|  |             scrollable.position.moveTo(scrollable.position.pixels + 50); | ||||||
|  |             return KeyEventResult.handled; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         node.focusInDirection(TraversalDirection.right); | ||||||
|  |         return KeyEventResult.handled; | ||||||
|  |       } else if (event.logicalKey == LogicalKeyboardKey.keyI) { | ||||||
|  |         setState(() { | ||||||
|  |           _mode = VimMode.insert; | ||||||
|  |         }); | ||||||
|  |         return KeyEventResult.handled; | ||||||
|  |       } | ||||||
|  |     } else if (_mode == VimMode.insert) { | ||||||
|  |       if (event.logicalKey == LogicalKeyboardKey.escape) { | ||||||
|  |         setState(() { | ||||||
|  |           _mode = VimMode.normal; | ||||||
|  |         }); | ||||||
|  |         // Unfocus the current widget to prevent typing | ||||||
|  |         node.unfocus(); | ||||||
|  |         return KeyEventResult.handled; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return KeyEventResult.ignored; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return Focus( | ||||||
|  |       focusNode: _focusScopeNode, | ||||||
|  |       onKeyEvent: _handleKeyEvent, | ||||||
|  |       child: widget.child, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user