Compare commits
11 Commits
3.1.0+121
...
c061ef2132
Author | SHA1 | Date | |
---|---|---|---|
|
c061ef2132 | ||
|
c378309bdd | ||
|
b2c5d64fc5 | ||
|
5371637b16 | ||
|
c5cbf0af37 | ||
|
1a31e22450 | ||
|
49db54529d | ||
|
8e0c0c6054 | ||
|
f3d1183076 | ||
|
a9f7f0cce0 | ||
|
f2943f8411 |
@@ -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();
|
||||||
|
}
|
BIN
android/app/src/main/res/drawable/ic_notification.png
Executable file
BIN
android/app/src/main/res/drawable/ic_notification.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
@@ -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
@@ -34,7 +34,7 @@ class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let serverUrl = UserDefaults.standard.getServerUrl()
|
let serverUrl = UserDefaults.standard.getServerUrl()
|
||||||
let url = "\(serverUrl)/chat/\(metadata["room_id"] ?? "")/messages"
|
let url = "\(serverUrl)/sphere/chat/\(metadata["room_id"] ?? "")/messages"
|
||||||
|
|
||||||
let parameters: [String: Any?] = [
|
let parameters: [String: Any?] = [
|
||||||
"content": textResponse.userText,
|
"content": textResponse.userText,
|
||||||
|
@@ -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;
|
||||||
|
@@ -339,7 +339,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await apiClient.post(
|
await apiClient.post(
|
||||||
'/chat/${chatRoom.value!.id}/members/me',
|
'/sphere/chat/${chatRoom.value!.id}/members/me',
|
||||||
);
|
);
|
||||||
ref.invalidate(chatroomIdentityProvider(id));
|
ref.invalidate(chatroomIdentityProvider(id));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -929,7 +929,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
if (attachment.isOnCloud) {
|
if (attachment.isOnCloud) {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
await client.delete(
|
await client.delete(
|
||||||
'/files/${attachment.data.id}',
|
'/drive/files/${attachment.data.id}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final clone = List.of(attachments.value);
|
final clone = List.of(attachments.value);
|
||||||
|
@@ -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,
|
||||||
|
@@ -305,7 +305,7 @@ class _UpdateSheetState extends State<_UpdateSheet> {
|
|||||||
UpdateModel model = UpdateModel(
|
UpdateModel model = UpdateModel(
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
"solian-update-${widget.release.tagName}.apk",
|
"solian-update-${widget.release.tagName}.apk",
|
||||||
"ic_launcher",
|
"launcher_icon",
|
||||||
'https://apps.apple.com/us/app/solian/id6499032345',
|
'https://apps.apple.com/us/app/solian/id6499032345',
|
||||||
);
|
);
|
||||||
AzhonAppUpdate.update(model);
|
AzhonAppUpdate.update(model);
|
||||||
|
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 3.1.0+121
|
version: 3.1.0+122
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.7.2
|
sdk: ^3.7.2
|
||||||
|
Reference in New Issue
Block a user