Merge branch 'main' into weblate-fluffychat-translations
This commit is contained in:
commit
f0841fea9d
15 changed files with 665 additions and 483 deletions
|
|
@ -20,7 +20,5 @@
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>13.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@ import UIKit
|
||||||
import Flutter
|
import Flutter
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
if #available(iOS 10.0, *) {
|
|
||||||
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
|
|
||||||
}
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,5 +112,26 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -440,25 +440,16 @@ class ChatListController extends State<ChatList>
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.open,
|
value: ChatContextAction.open,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: .min,
|
|
||||||
spacing: 12.0,
|
spacing: 12.0,
|
||||||
children: [
|
children: [
|
||||||
Avatar(mxContent: room.avatar, name: displayname),
|
Avatar(mxContent: room.avatar, name: displayname, size: 24),
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 128),
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
child: Text(
|
child: Text(displayname, maxLines: 1, overflow: .ellipsis),
|
||||||
displayname,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const PopupMenuDivider(),
|
|
||||||
if (space != null)
|
if (space != null)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.goToSpace,
|
value: ChatContextAction.goToSpace,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/l10n/l10n.dart';
|
import 'package:fluffychat/l10n/l10n.dart';
|
||||||
|
|
@ -81,6 +82,9 @@ class LoginController extends State<Login> {
|
||||||
password: passwordController.text,
|
password: passwordController.text,
|
||||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
initialDeviceDisplayName: PlatformInfos.clientName,
|
||||||
);
|
);
|
||||||
|
if (mounted) {
|
||||||
|
context.go('/backup');
|
||||||
|
}
|
||||||
} on MatrixException catch (exception) {
|
} on MatrixException catch (exception) {
|
||||||
setState(() => passwordError = exception.errorMessage);
|
setState(() => passwordError = exception.errorMessage);
|
||||||
return setState(() => loading = false);
|
return setState(() => loading = false);
|
||||||
|
|
|
||||||
|
|
@ -37,164 +37,165 @@ class SignInPage extends StatelessWidget {
|
||||||
? L10n.of(context).createNewAccount
|
? L10n.of(context).createNewAccount
|
||||||
: L10n.of(context).login,
|
: L10n.of(context).login,
|
||||||
),
|
),
|
||||||
bottom: PreferredSize(
|
),
|
||||||
preferredSize: const Size.fromHeight(56 + 60),
|
body: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Column(
|
||||||
child: Column(
|
spacing: 16,
|
||||||
mainAxisSize: .min,
|
children: [
|
||||||
crossAxisAlignment: .center,
|
SelectableText(
|
||||||
spacing: 12,
|
signUp
|
||||||
children: [
|
? L10n.of(context).signUpGreeting
|
||||||
SelectableText(
|
: L10n.of(context).signInGreeting,
|
||||||
signUp
|
textAlign: .center,
|
||||||
? L10n.of(context).signUpGreeting
|
),
|
||||||
: L10n.of(context).signInGreeting,
|
TextField(
|
||||||
textAlign: .center,
|
readOnly:
|
||||||
|
state.publicHomeservers.connectionState ==
|
||||||
|
ConnectionState.waiting,
|
||||||
|
controller: viewModel.filterTextController,
|
||||||
|
autocorrect: false,
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
fillColor: theme.colorScheme.secondaryContainer,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
borderRadius: BorderRadius.circular(99),
|
||||||
),
|
),
|
||||||
TextField(
|
errorText: state.publicHomeservers.error?.toLocalizedString(
|
||||||
readOnly:
|
context,
|
||||||
state.publicHomeservers.connectionState ==
|
),
|
||||||
ConnectionState.waiting,
|
prefixIcon: const Icon(Icons.search_outlined),
|
||||||
controller: viewModel.filterTextController,
|
hintText: L10n.of(context).searchOrEnterHomeserverAddress,
|
||||||
autocorrect: false,
|
),
|
||||||
keyboardType: TextInputType.url,
|
),
|
||||||
decoration: InputDecoration(
|
if (state.publicHomeservers.connectionState ==
|
||||||
filled: true,
|
ConnectionState.done)
|
||||||
fillColor: theme.colorScheme.secondaryContainer,
|
Expanded(
|
||||||
border: OutlineInputBorder(
|
child: Material(
|
||||||
borderSide: BorderSide.none,
|
borderRadius: BorderRadius.circular(
|
||||||
borderRadius: BorderRadius.circular(99),
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
color: theme.colorScheme.surfaceContainerLow,
|
||||||
|
child: RadioGroup<PublicHomeserverData>(
|
||||||
|
groupValue: state.selectedHomeserver,
|
||||||
|
onChanged: viewModel.selectHomeserver,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: publicHomeservers.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final server = publicHomeservers[i];
|
||||||
|
final website = server.website;
|
||||||
|
return RadioListTile.adaptive(
|
||||||
|
value: server,
|
||||||
|
radioScaleFactor:
|
||||||
|
FluffyThemes.isColumnMode(context) ||
|
||||||
|
{
|
||||||
|
TargetPlatform.iOS,
|
||||||
|
TargetPlatform.macOS,
|
||||||
|
}.contains(theme.platform)
|
||||||
|
? 2
|
||||||
|
: 1,
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(server.name ?? 'Unknown'),
|
||||||
|
),
|
||||||
|
if (website != null)
|
||||||
|
SizedBox.square(
|
||||||
|
dimension: 32,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.open_in_new_outlined,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
launchUrlString(website),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
spacing: 4.0,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (server.features?.isNotEmpty == true)
|
||||||
|
Wrap(
|
||||||
|
spacing: 4.0,
|
||||||
|
runSpacing: 4.0,
|
||||||
|
children: [
|
||||||
|
...?server.languages?.map(
|
||||||
|
(language) => Material(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
|
color: theme
|
||||||
|
.colorScheme
|
||||||
|
.tertiaryContainer,
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6.0,
|
||||||
|
vertical: 3.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
language,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: theme
|
||||||
|
.colorScheme
|
||||||
|
.onTertiaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...server.features!.map(
|
||||||
|
(feature) => Material(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
|
color: theme
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6.0,
|
||||||
|
vertical: 3.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
feature,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: theme
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
server.description ?? 'A matrix homeserver',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
errorText: state.publicHomeservers.error
|
|
||||||
?.toLocalizedString(context),
|
|
||||||
prefixIcon: const Icon(Icons.search_outlined),
|
|
||||||
hintText: L10n.of(
|
|
||||||
context,
|
|
||||||
).searchOrEnterHomeserverAddress,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
),
|
else
|
||||||
),
|
Center(child: CircularProgressIndicator.adaptive()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: state.publicHomeservers.connectionState == ConnectionState.done
|
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Material(
|
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
color: theme.colorScheme.surfaceContainerLow,
|
|
||||||
child: RadioGroup<PublicHomeserverData>(
|
|
||||||
groupValue: state.selectedHomeserver,
|
|
||||||
onChanged: viewModel.selectHomeserver,
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: publicHomeservers.length,
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
final server = publicHomeservers[i];
|
|
||||||
final homepage = server.homepage;
|
|
||||||
return RadioListTile.adaptive(
|
|
||||||
value: server,
|
|
||||||
radioScaleFactor:
|
|
||||||
FluffyThemes.isColumnMode(context) ||
|
|
||||||
{
|
|
||||||
TargetPlatform.iOS,
|
|
||||||
TargetPlatform.macOS,
|
|
||||||
}.contains(theme.platform)
|
|
||||||
? 2
|
|
||||||
: 1,
|
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: Text(server.name ?? 'Unknown')),
|
|
||||||
if (homepage != null)
|
|
||||||
SizedBox.square(
|
|
||||||
dimension: 32,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.open_in_new_outlined,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
launchUrlString(homepage),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
subtitle: Column(
|
|
||||||
spacing: 4.0,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
if (server.features?.isNotEmpty == true)
|
|
||||||
Wrap(
|
|
||||||
spacing: 4.0,
|
|
||||||
runSpacing: 4.0,
|
|
||||||
children: [
|
|
||||||
...?server.languages?.map(
|
|
||||||
(language) => Material(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
AppConfig.borderRadius,
|
|
||||||
),
|
|
||||||
color: theme
|
|
||||||
.colorScheme
|
|
||||||
.tertiaryContainer,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6.0,
|
|
||||||
vertical: 3.0,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
language,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
color: theme
|
|
||||||
.colorScheme
|
|
||||||
.onTertiaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...server.features!.map(
|
|
||||||
(feature) => Material(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
AppConfig.borderRadius,
|
|
||||||
),
|
|
||||||
color: theme
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6.0,
|
|
||||||
vertical: 3.0,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
feature,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
color: theme
|
|
||||||
.colorScheme
|
|
||||||
.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
server.description ?? 'A matrix homeserver',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Center(child: CircularProgressIndicator.adaptive()),
|
|
||||||
bottomNavigationBar: AnimatedSize(
|
bottomNavigationBar: AnimatedSize(
|
||||||
duration: FluffyThemes.animationDuration,
|
duration: FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
|
|
@ -203,12 +204,14 @@ class SignInPage extends StatelessWidget {
|
||||||
!publicHomeservers.contains(selectedHomserver)
|
!publicHomeservers.contains(selectedHomserver)
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: Material(
|
: Material(
|
||||||
elevation: 8,
|
|
||||||
shadowColor: theme.appBarTheme.shadowColor,
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: theme.colorScheme.primary,
|
||||||
|
foregroundColor: theme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
onPressed:
|
onPressed:
|
||||||
state.loginLoading.connectionState ==
|
state.loginLoading.connectionState ==
|
||||||
ConnectionState.waiting
|
ConnectionState.waiting
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ int sortHomeservers(PublicHomeserverData a, PublicHomeserverData b) {
|
||||||
int _calcHomeserverScore(PublicHomeserverData homeserver) {
|
int _calcHomeserverScore(PublicHomeserverData homeserver) {
|
||||||
var score = 0;
|
var score = 0;
|
||||||
if (homeserver.description?.isNotEmpty == true) score++;
|
if (homeserver.description?.isNotEmpty == true) score++;
|
||||||
if (homeserver.homepage?.isNotEmpty == true) score++;
|
if (homeserver.website?.isNotEmpty == true) score++;
|
||||||
score += (homeserver.languages?.length ?? 0);
|
score += (homeserver.languages?.length ?? 0);
|
||||||
score += (homeserver.features?.length ?? 0);
|
score += (homeserver.features?.length ?? 0);
|
||||||
score += (homeserver.onlineStatus ?? 0);
|
score += (homeserver.onlineStatus ?? 0);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
class PublicHomeserverData {
|
class PublicHomeserverData {
|
||||||
final String? name;
|
final String? name;
|
||||||
final String? clientDomain;
|
final String? clientDomain;
|
||||||
final String? homepage;
|
final String? website;
|
||||||
final String? isp;
|
final String? isp;
|
||||||
final String? staffJur;
|
final String? staffJur;
|
||||||
final String? rules;
|
final String? rules;
|
||||||
|
|
@ -26,7 +26,7 @@ class PublicHomeserverData {
|
||||||
PublicHomeserverData({
|
PublicHomeserverData({
|
||||||
this.name,
|
this.name,
|
||||||
this.clientDomain,
|
this.clientDomain,
|
||||||
this.homepage,
|
this.website,
|
||||||
this.isp,
|
this.isp,
|
||||||
this.staffJur,
|
this.staffJur,
|
||||||
this.rules,
|
this.rules,
|
||||||
|
|
@ -53,7 +53,7 @@ class PublicHomeserverData {
|
||||||
return PublicHomeserverData(
|
return PublicHomeserverData(
|
||||||
name: json['name'],
|
name: json['name'],
|
||||||
clientDomain: json['client_domain'],
|
clientDomain: json['client_domain'],
|
||||||
homepage: json['homepage'],
|
website: json['website'],
|
||||||
isp: json['isp'],
|
isp: json['isp'],
|
||||||
staffJur: json['staff_jur'],
|
staffJur: json['staff_jur'],
|
||||||
rules: json['rules'],
|
rules: json['rules'],
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:fluffychat/config/setting_keys.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
import 'package:fluffychat/config/setting_keys.dart';
|
||||||
import 'package:fluffychat/l10n/l10n.dart';
|
import 'package:fluffychat/l10n/l10n.dart';
|
||||||
import 'package:fluffychat/pages/sign_in/view_model/model/public_homeserver_data.dart';
|
import 'package:fluffychat/pages/sign_in/view_model/model/public_homeserver_data.dart';
|
||||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||||
|
|
@ -70,6 +70,7 @@ Future<void> connectToHomeserverFlow(
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
setState(AsyncSnapshot.withData(ConnectionState.done, true));
|
setState(AsyncSnapshot.withData(ConnectionState.done, true));
|
||||||
|
context.go('/backup');
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
setState(AsyncSnapshot.withError(ConnectionState.done, e, s));
|
setState(AsyncSnapshot.withError(ConnectionState.done, e, s));
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,77 @@ class AdaptiveDialogAction extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AdaptiveDialogInkWell extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
|
const AdaptiveDialogInkWell({
|
||||||
|
super.key,
|
||||||
|
required this.onTap,
|
||||||
|
required this.child,
|
||||||
|
this.padding = const EdgeInsets.all(16),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
if ({TargetPlatform.iOS, TargetPlatform.macOS}.contains(theme.platform)) {
|
||||||
|
return CupertinoButton(
|
||||||
|
onPressed: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||||
|
color: theme.colorScheme.surfaceBright,
|
||||||
|
padding: padding,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Material(
|
||||||
|
color: theme.colorScheme.surfaceBright,
|
||||||
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Center(child: child),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdaptiveIconTextButton extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
const AdaptiveIconTextButton({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
required this.icon,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final color = Theme.of(context).colorScheme.secondary;
|
||||||
|
return Expanded(
|
||||||
|
child: AdaptiveDialogInkWell(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
onTap: onTap,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: .min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: color),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(fontSize: 12, color: color),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: .ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import 'package:go_router/go_router.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/l10n/l10n.dart';
|
import 'package:fluffychat/l10n/l10n.dart';
|
||||||
|
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||||
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||||
import '../../config/themes.dart';
|
import '../../config/themes.dart';
|
||||||
import '../../utils/url_launcher.dart';
|
import '../../utils/url_launcher.dart';
|
||||||
import '../avatar.dart';
|
import '../avatar.dart';
|
||||||
|
|
@ -90,15 +92,8 @@ class PublicRoomDialog extends StatelessWidget {
|
||||||
final roomLink = roomAlias ?? chunk?.roomId;
|
final roomLink = roomAlias ?? chunk?.roomId;
|
||||||
var copied = false;
|
var copied = false;
|
||||||
return AlertDialog.adaptive(
|
return AlertDialog.adaptive(
|
||||||
title: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 256),
|
|
||||||
child: Text(
|
|
||||||
chunk?.name ?? roomAlias?.localpart ?? chunk?.roomId ?? 'Unknown',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 256, maxHeight: 256),
|
constraints: const BoxConstraints(maxWidth: 256),
|
||||||
child: FutureBuilder<PublishedRoomsChunk>(
|
child: FutureBuilder<PublishedRoomsChunk>(
|
||||||
future: _search(context),
|
future: _search(context),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|
@ -109,125 +104,196 @@ class PublicRoomDialog extends StatelessWidget {
|
||||||
final topic = profile?.topic;
|
final topic = profile?.topic;
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 8,
|
spacing: 16,
|
||||||
mainAxisSize: .min,
|
mainAxisSize: .min,
|
||||||
crossAxisAlignment: .stretch,
|
crossAxisAlignment: .stretch,
|
||||||
children: [
|
children: [
|
||||||
if (roomLink != null)
|
Row(
|
||||||
HoverBuilder(
|
spacing: 12,
|
||||||
builder: (context, hovered) => StatefulBuilder(
|
children: [
|
||||||
builder: (context, setState) => MouseRegion(
|
Avatar(
|
||||||
cursor: SystemMouseCursors.click,
|
mxContent: avatar,
|
||||||
child: GestureDetector(
|
name: profile?.name ?? roomLink,
|
||||||
onTap: () {
|
size: Avatar.defaultSize * 1.5,
|
||||||
Clipboard.setData(ClipboardData(text: roomLink));
|
onTap: avatar != null
|
||||||
setState(() {
|
? () => showDialog(
|
||||||
copied = true;
|
context: context,
|
||||||
});
|
builder: (_) => MxcImageViewer(avatar),
|
||||||
},
|
)
|
||||||
child: RichText(
|
: null,
|
||||||
text: TextSpan(
|
),
|
||||||
children: [
|
Expanded(
|
||||||
WidgetSpan(
|
child: Column(
|
||||||
child: Padding(
|
crossAxisAlignment: .start,
|
||||||
padding: const EdgeInsets.only(
|
children: [
|
||||||
right: 4.0,
|
Text(
|
||||||
),
|
profile?.name ??
|
||||||
child: AnimatedScale(
|
roomLink ??
|
||||||
duration:
|
profile?.roomId ??
|
||||||
FluffyThemes.animationDuration,
|
' - ',
|
||||||
curve: FluffyThemes.animationCurve,
|
maxLines: 1,
|
||||||
scale: hovered
|
overflow: .ellipsis,
|
||||||
? 1.33
|
style: TextStyle(fontSize: 16),
|
||||||
: copied
|
),
|
||||||
? 1.25
|
const SizedBox(height: 8),
|
||||||
: 1.0,
|
if (roomLink != null)
|
||||||
child: Icon(
|
HoverBuilder(
|
||||||
copied
|
builder: (context, hovered) => StatefulBuilder(
|
||||||
? Icons.check_circle
|
builder: (context, setState) => MouseRegion(
|
||||||
: Icons.copy,
|
cursor: SystemMouseCursors.click,
|
||||||
size: 12,
|
child: GestureDetector(
|
||||||
color: copied ? Colors.green : null,
|
onTap: () {
|
||||||
|
Clipboard.setData(
|
||||||
|
ClipboardData(text: roomLink),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
copied = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
WidgetSpan(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 4.0,
|
||||||
|
),
|
||||||
|
child: AnimatedScale(
|
||||||
|
duration: FluffyThemes
|
||||||
|
.animationDuration,
|
||||||
|
curve: FluffyThemes
|
||||||
|
.animationCurve,
|
||||||
|
scale: hovered
|
||||||
|
? 1.33
|
||||||
|
: copied
|
||||||
|
? 1.25
|
||||||
|
: 1.0,
|
||||||
|
child: Icon(
|
||||||
|
copied
|
||||||
|
? Icons.check_circle
|
||||||
|
: Icons.copy,
|
||||||
|
size: 12,
|
||||||
|
color: copied
|
||||||
|
? Colors.green
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(text: roomLink),
|
||||||
|
],
|
||||||
|
style: theme.textTheme.bodyMedium
|
||||||
|
?.copyWith(fontSize: 10),
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(text: roomLink),
|
|
||||||
],
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
|
||||||
|
if (profile?.numJoinedMembers != null)
|
||||||
|
Text(
|
||||||
|
L10n.of(context).countParticipants(
|
||||||
|
profile?.numJoinedMembers ?? 0,
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (topic != null && topic.isNotEmpty)
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxHeight: 200),
|
||||||
|
child: Scrollbar(
|
||||||
|
thumbVisibility: true,
|
||||||
|
trackVisibility: true,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: SelectableLinkify(
|
||||||
|
text: topic,
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
|
context,
|
||||||
|
).scale(1),
|
||||||
|
textAlign: .start,
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
|
onOpen: (url) =>
|
||||||
|
UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Center(
|
|
||||||
child: Avatar(
|
Row(
|
||||||
mxContent: avatar,
|
mainAxisAlignment: .spaceBetween,
|
||||||
name: profile?.name ?? roomLink,
|
spacing: 4,
|
||||||
size: Avatar.defaultSize * 2,
|
children: [
|
||||||
onTap: avatar != null
|
AdaptiveIconTextButton(
|
||||||
? () => showDialog(
|
label: L10n.of(context).report,
|
||||||
context: context,
|
icon: Icons.gavel_outlined,
|
||||||
builder: (_) => MxcImageViewer(avatar),
|
onTap: () async {
|
||||||
)
|
Navigator.of(context).pop();
|
||||||
: null,
|
final reason = await showTextInputDialog(
|
||||||
|
context: context,
|
||||||
|
title: L10n.of(context).whyDoYouWantToReportThis,
|
||||||
|
okLabel: L10n.of(context).report,
|
||||||
|
cancelLabel: L10n.of(context).cancel,
|
||||||
|
hintText: L10n.of(context).reason,
|
||||||
|
);
|
||||||
|
if (reason == null || reason.isEmpty) return;
|
||||||
|
await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => Matrix.of(context).client.reportRoom(
|
||||||
|
chunk?.roomId ?? roomAlias!,
|
||||||
|
reason,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AdaptiveIconTextButton(
|
||||||
|
label: L10n.of(context).copy,
|
||||||
|
icon: Icons.copy_outlined,
|
||||||
|
onTap: () =>
|
||||||
|
Clipboard.setData(ClipboardData(text: roomLink!)),
|
||||||
|
),
|
||||||
|
AdaptiveIconTextButton(
|
||||||
|
label: L10n.of(context).share,
|
||||||
|
icon: Icons.adaptive.share,
|
||||||
|
onTap: () => FluffyShare.share(
|
||||||
|
'https://matrix.to/#/$roomLink',
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AdaptiveDialogInkWell(
|
||||||
|
onTap: () => _joinRoom(context),
|
||||||
|
child: Text(
|
||||||
|
chunk?.joinRule == 'knock' &&
|
||||||
|
Matrix.of(
|
||||||
|
context,
|
||||||
|
).client.getRoomById(chunk!.roomId) ==
|
||||||
|
null
|
||||||
|
? L10n.of(context).knock
|
||||||
|
: chunk?.roomType == 'm.space'
|
||||||
|
? L10n.of(context).joinSpace
|
||||||
|
: L10n.of(context).joinRoom,
|
||||||
|
style: TextStyle(color: theme.colorScheme.secondary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (profile?.numJoinedMembers != null)
|
|
||||||
Text(
|
|
||||||
L10n.of(
|
|
||||||
context,
|
|
||||||
).countParticipants(profile?.numJoinedMembers ?? 0),
|
|
||||||
style: const TextStyle(fontSize: 10),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
if (topic != null && topic.isNotEmpty)
|
|
||||||
SelectableLinkify(
|
|
||||||
text: topic,
|
|
||||||
textScaleFactor: MediaQuery.textScalerOf(
|
|
||||||
context,
|
|
||||||
).scale(1),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
options: const LinkifyOptions(humanize: false),
|
|
||||||
linkStyle: TextStyle(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
decorationColor: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
onOpen: (url) =>
|
|
||||||
UrlLauncher(context, url.url).launchUrl(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
AdaptiveDialogAction(
|
|
||||||
bigButtons: true,
|
|
||||||
borderRadius: AdaptiveDialogAction.topRadius,
|
|
||||||
onPressed: () => _joinRoom(context),
|
|
||||||
child: Text(
|
|
||||||
chunk?.joinRule == 'knock' &&
|
|
||||||
Matrix.of(context).client.getRoomById(chunk!.roomId) == null
|
|
||||||
? L10n.of(context).knock
|
|
||||||
: chunk?.roomType == 'm.space'
|
|
||||||
? L10n.of(context).joinSpace
|
|
||||||
: L10n.of(context).joinRoom,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AdaptiveDialogAction(
|
|
||||||
bigButtons: true,
|
|
||||||
borderRadius: AdaptiveDialogAction.bottomRadius,
|
|
||||||
onPressed: Navigator.of(context).pop,
|
|
||||||
child: Text(L10n.of(context).close),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ import 'package:matrix/matrix.dart';
|
||||||
import 'package:fluffychat/config/themes.dart';
|
import 'package:fluffychat/config/themes.dart';
|
||||||
import 'package:fluffychat/l10n/l10n.dart';
|
import 'package:fluffychat/l10n/l10n.dart';
|
||||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||||
|
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||||
|
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/presence_builder.dart';
|
import 'package:fluffychat/widgets/presence_builder.dart';
|
||||||
import '../../utils/url_launcher.dart';
|
import '../../utils/url_launcher.dart';
|
||||||
|
|
@ -17,6 +19,8 @@ import '../hover_builder.dart';
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
import '../mxc_image_viewer.dart';
|
import '../mxc_image_viewer.dart';
|
||||||
|
|
||||||
|
// ignore: unused_import
|
||||||
|
|
||||||
class UserDialog extends StatelessWidget {
|
class UserDialog extends StatelessWidget {
|
||||||
static Future<void> show({
|
static Future<void> show({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
|
|
@ -37,7 +41,6 @@ class UserDialog extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final dmRoomId = client.getDirectChatFromUserId(profile.userId);
|
|
||||||
final displayname =
|
final displayname =
|
||||||
profile.displayName ??
|
profile.displayName ??
|
||||||
profile.userId.localpart ??
|
profile.userId.localpart ??
|
||||||
|
|
@ -46,168 +49,209 @@ class UserDialog extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final avatar = profile.avatarUrl;
|
final avatar = profile.avatarUrl;
|
||||||
return AlertDialog.adaptive(
|
return AlertDialog.adaptive(
|
||||||
title: ConstrainedBox(
|
content: PresenceBuilder(
|
||||||
constraints: const BoxConstraints(maxWidth: 256),
|
userId: profile.userId,
|
||||||
child: Center(child: Text(displayname, textAlign: TextAlign.center)),
|
client: Matrix.of(context).client,
|
||||||
),
|
builder: (context, presence) {
|
||||||
content: ConstrainedBox(
|
if (presence == null) return const SizedBox.shrink();
|
||||||
constraints: const BoxConstraints(maxWidth: 256, maxHeight: 256),
|
final statusMsg = presence.statusMsg;
|
||||||
child: PresenceBuilder(
|
final lastActiveTimestamp = presence.lastActiveTimestamp;
|
||||||
userId: profile.userId,
|
final presenceText = presence.currentlyActive == true
|
||||||
client: Matrix.of(context).client,
|
? L10n.of(context).currentlyActive
|
||||||
builder: (context, presence) {
|
: lastActiveTimestamp != null
|
||||||
if (presence == null) return const SizedBox.shrink();
|
? L10n.of(
|
||||||
final statusMsg = presence.statusMsg;
|
context,
|
||||||
final lastActiveTimestamp = presence.lastActiveTimestamp;
|
).lastActiveAgo(lastActiveTimestamp.localizedTimeShort(context))
|
||||||
final presenceText = presence.currentlyActive == true
|
: null;
|
||||||
? L10n.of(context).currentlyActive
|
return Column(
|
||||||
: lastActiveTimestamp != null
|
spacing: 16,
|
||||||
? L10n.of(context).lastActiveAgo(
|
mainAxisSize: .min,
|
||||||
lastActiveTimestamp.localizedTimeShort(context),
|
crossAxisAlignment: .stretch,
|
||||||
)
|
children: [
|
||||||
: null;
|
Row(
|
||||||
return SingleChildScrollView(
|
spacing: 12,
|
||||||
child: Column(
|
|
||||||
spacing: 8,
|
|
||||||
mainAxisSize: .min,
|
|
||||||
crossAxisAlignment: .stretch,
|
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Avatar(
|
||||||
child: Avatar(
|
mxContent: avatar,
|
||||||
mxContent: avatar,
|
name: displayname,
|
||||||
name: displayname,
|
size: Avatar.defaultSize * 1.5,
|
||||||
size: Avatar.defaultSize * 2,
|
onTap: avatar != null
|
||||||
onTap: avatar != null
|
? () => showDialog(
|
||||||
? () => showDialog(
|
context: context,
|
||||||
context: context,
|
builder: (_) => MxcImageViewer(avatar),
|
||||||
builder: (_) => MxcImageViewer(avatar),
|
)
|
||||||
)
|
: null,
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
HoverBuilder(
|
Expanded(
|
||||||
builder: (context, hovered) => StatefulBuilder(
|
child: Column(
|
||||||
builder: (context, setState) => MouseRegion(
|
crossAxisAlignment: .start,
|
||||||
cursor: SystemMouseCursors.click,
|
children: [
|
||||||
child: GestureDetector(
|
Text(
|
||||||
onTap: () {
|
displayname,
|
||||||
Clipboard.setData(
|
maxLines: 1,
|
||||||
ClipboardData(text: profile.userId),
|
overflow: .ellipsis,
|
||||||
);
|
style: TextStyle(fontSize: 16),
|
||||||
setState(() {
|
),
|
||||||
copied = true;
|
const SizedBox(height: 8),
|
||||||
});
|
HoverBuilder(
|
||||||
},
|
builder: (context, hovered) => StatefulBuilder(
|
||||||
child: RichText(
|
builder: (context, setState) => MouseRegion(
|
||||||
text: TextSpan(
|
cursor: SystemMouseCursors.click,
|
||||||
children: [
|
child: GestureDetector(
|
||||||
WidgetSpan(
|
onTap: () {
|
||||||
child: Padding(
|
Clipboard.setData(
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
ClipboardData(text: profile.userId),
|
||||||
child: AnimatedScale(
|
);
|
||||||
duration: FluffyThemes.animationDuration,
|
setState(() {
|
||||||
curve: FluffyThemes.animationCurve,
|
copied = true;
|
||||||
scale: hovered
|
});
|
||||||
? 1.33
|
},
|
||||||
: copied
|
child: RichText(
|
||||||
? 1.25
|
text: TextSpan(
|
||||||
: 1.0,
|
children: [
|
||||||
child: Icon(
|
WidgetSpan(
|
||||||
copied
|
child: Padding(
|
||||||
? Icons.check_circle
|
padding: const EdgeInsets.only(
|
||||||
: Icons.copy,
|
right: 4.0,
|
||||||
size: 12,
|
),
|
||||||
color: copied ? Colors.green : null,
|
child: AnimatedScale(
|
||||||
|
duration:
|
||||||
|
FluffyThemes.animationDuration,
|
||||||
|
curve: FluffyThemes.animationCurve,
|
||||||
|
scale: hovered
|
||||||
|
? 1.33
|
||||||
|
: copied
|
||||||
|
? 1.25
|
||||||
|
: 1.0,
|
||||||
|
child: Icon(
|
||||||
|
copied
|
||||||
|
? Icons.check_circle
|
||||||
|
: Icons.copy,
|
||||||
|
size: 12,
|
||||||
|
color: copied
|
||||||
|
? Colors.green
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
TextSpan(text: profile.userId),
|
||||||
|
],
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: .ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
TextSpan(text: profile.userId),
|
|
||||||
],
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (presenceText != null)
|
||||||
|
Text(
|
||||||
|
presenceText,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (statusMsg != null)
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxHeight: 200),
|
||||||
|
child: Scrollbar(
|
||||||
|
thumbVisibility: true,
|
||||||
|
trackVisibility: true,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: SelectableLinkify(
|
||||||
|
text: statusMsg,
|
||||||
|
textScaleFactor: MediaQuery.textScalerOf(
|
||||||
|
context,
|
||||||
|
).scale(1),
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
options: const LinkifyOptions(humanize: false),
|
||||||
|
linkStyle: TextStyle(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
decorationColor: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
onOpen: (url) =>
|
||||||
|
UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (presenceText != null)
|
),
|
||||||
Text(
|
Row(
|
||||||
presenceText,
|
mainAxisAlignment: .spaceBetween,
|
||||||
style: const TextStyle(fontSize: 10),
|
spacing: 4,
|
||||||
textAlign: TextAlign.center,
|
children: [
|
||||||
),
|
AdaptiveIconTextButton(
|
||||||
if (statusMsg != null)
|
label: L10n.of(context).block,
|
||||||
SelectableLinkify(
|
icon: Icons.block_outlined,
|
||||||
text: statusMsg,
|
onTap: () {
|
||||||
textScaleFactor: MediaQuery.textScalerOf(
|
final router = GoRouter.of(context);
|
||||||
context,
|
Navigator.of(context).pop();
|
||||||
).scale(1),
|
router.go(
|
||||||
textAlign: TextAlign.center,
|
'/rooms/settings/security/ignorelist',
|
||||||
options: const LinkifyOptions(humanize: false),
|
extra: profile.userId,
|
||||||
linkStyle: TextStyle(
|
);
|
||||||
color: theme.colorScheme.primary,
|
},
|
||||||
decoration: TextDecoration.underline,
|
),
|
||||||
decorationColor: theme.colorScheme.primary,
|
AdaptiveIconTextButton(
|
||||||
),
|
label: L10n.of(context).report,
|
||||||
onOpen: (url) =>
|
icon: Icons.gavel_outlined,
|
||||||
UrlLauncher(context, url.url).launchUrl(),
|
onTap: () async {
|
||||||
),
|
Navigator.of(context).pop();
|
||||||
|
final reason = await showTextInputDialog(
|
||||||
|
context: context,
|
||||||
|
title: L10n.of(context).whyDoYouWantToReportThis,
|
||||||
|
okLabel: L10n.of(context).report,
|
||||||
|
cancelLabel: L10n.of(context).cancel,
|
||||||
|
hintText: L10n.of(context).reason,
|
||||||
|
);
|
||||||
|
if (reason == null || reason.isEmpty) return;
|
||||||
|
await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => Matrix.of(
|
||||||
|
context,
|
||||||
|
).client.reportUser(profile.userId, reason),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AdaptiveIconTextButton(
|
||||||
|
label: L10n.of(context).share,
|
||||||
|
icon: Icons.adaptive.share,
|
||||||
|
onTap: () => FluffyShare.share(profile.userId, context),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
AdaptiveDialogInkWell(
|
||||||
},
|
onTap: () async {
|
||||||
),
|
final router = GoRouter.of(context);
|
||||||
|
final roomIdResult = await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => client.startDirectChat(profile.userId),
|
||||||
|
);
|
||||||
|
final roomId = roomIdResult.result;
|
||||||
|
if (roomId == null) return;
|
||||||
|
if (context.mounted) Navigator.of(context).pop();
|
||||||
|
router.go('/rooms/$roomId');
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
L10n.of(context).sendAMessage,
|
||||||
|
style: TextStyle(color: theme.colorScheme.secondary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
|
||||||
if (client.userID != profile.userId) ...[
|
|
||||||
AdaptiveDialogAction(
|
|
||||||
borderRadius: AdaptiveDialogAction.topRadius,
|
|
||||||
bigButtons: true,
|
|
||||||
onPressed: () async {
|
|
||||||
final router = GoRouter.of(context);
|
|
||||||
final roomIdResult = await showFutureLoadingDialog(
|
|
||||||
context: context,
|
|
||||||
future: () => client.startDirectChat(profile.userId),
|
|
||||||
);
|
|
||||||
final roomId = roomIdResult.result;
|
|
||||||
if (roomId == null) return;
|
|
||||||
if (context.mounted) Navigator.of(context).pop();
|
|
||||||
router.go('/rooms/$roomId');
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
dmRoomId == null
|
|
||||||
? L10n.of(context).startConversation
|
|
||||||
: L10n.of(context).sendAMessage,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AdaptiveDialogAction(
|
|
||||||
bigButtons: true,
|
|
||||||
borderRadius: AdaptiveDialogAction.centerRadius,
|
|
||||||
onPressed: () {
|
|
||||||
final router = GoRouter.of(context);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
router.go(
|
|
||||||
'/rooms/settings/security/ignorelist',
|
|
||||||
extra: profile.userId,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
L10n.of(context).ignoreUser,
|
|
||||||
style: TextStyle(color: theme.colorScheme.error),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
AdaptiveDialogAction(
|
|
||||||
bigButtons: true,
|
|
||||||
borderRadius: AdaptiveDialogAction.bottomRadius,
|
|
||||||
onPressed: Navigator.of(context).pop,
|
|
||||||
child: Text(L10n.of(context).close),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
final onRoomKeyRequestSub = <String, StreamSubscription>{};
|
final onRoomKeyRequestSub = <String, StreamSubscription>{};
|
||||||
final onKeyVerificationRequestSub = <String, StreamSubscription>{};
|
final onKeyVerificationRequestSub = <String, StreamSubscription>{};
|
||||||
final onNotification = <String, StreamSubscription>{};
|
final onNotification = <String, StreamSubscription>{};
|
||||||
final onLoginStateChanged = <String, StreamSubscription<LoginState>>{};
|
final onLogoutSub = <String, StreamSubscription<LoginState>>{};
|
||||||
final onUiaRequest = <String, StreamSubscription<UiaRequest>>{};
|
final onUiaRequest = <String, StreamSubscription<UiaRequest>>{};
|
||||||
|
|
||||||
String? _cachedPassword;
|
String? _cachedPassword;
|
||||||
|
|
@ -255,31 +255,29 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
onLoginStateChanged[name] ??= c.onLoginStateChanged.stream.listen((state) {
|
onLogoutSub[name] ??= c.onLoginStateChanged.stream
|
||||||
final loggedInWithMultipleClients = widget.clients.length > 1;
|
.where((state) => state == LoginState.loggedOut)
|
||||||
if (state == LoginState.loggedOut) {
|
.listen((state) {
|
||||||
_cancelSubs(c.clientName);
|
final loggedInWithMultipleClients = widget.clients.length > 1;
|
||||||
widget.clients.remove(c);
|
|
||||||
ClientManager.removeClientNameFromStore(c.clientName, store);
|
|
||||||
InitWithRestoreExtension.deleteSessionBackup(name);
|
|
||||||
}
|
|
||||||
if (loggedInWithMultipleClients && state != LoginState.loggedIn) {
|
|
||||||
ScaffoldMessenger.of(
|
|
||||||
FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ??
|
|
||||||
context,
|
|
||||||
).showSnackBar(
|
|
||||||
SnackBar(content: Text(L10n.of(context).oneClientLoggedOut)),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (state != LoginState.loggedIn) {
|
_cancelSubs(c.clientName);
|
||||||
FluffyChatApp.router.go('/rooms');
|
widget.clients.remove(c);
|
||||||
}
|
ClientManager.removeClientNameFromStore(c.clientName, store);
|
||||||
} else {
|
InitWithRestoreExtension.deleteSessionBackup(name);
|
||||||
FluffyChatApp.router.go(
|
|
||||||
state == LoginState.loggedIn ? '/backup' : '/home',
|
if (loggedInWithMultipleClients) {
|
||||||
);
|
ScaffoldMessenger.of(
|
||||||
}
|
FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ??
|
||||||
});
|
context,
|
||||||
|
).showSnackBar(
|
||||||
|
SnackBar(content: Text(L10n.of(context).oneClientLoggedOut)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (state != LoginState.loggedIn) {
|
||||||
|
FluffyChatApp.router.go('/rooms');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
onUiaRequest[name] ??= c.onUiaRequest.stream.listen(uiaRequestHandler);
|
onUiaRequest[name] ??= c.onUiaRequest.stream.listen(uiaRequestHandler);
|
||||||
if (PlatformInfos.isWeb || PlatformInfos.isLinux) {
|
if (PlatformInfos.isWeb || PlatformInfos.isLinux) {
|
||||||
c.onSync.stream.first.then((s) {
|
c.onSync.stream.first.then((s) {
|
||||||
|
|
@ -296,8 +294,8 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
onRoomKeyRequestSub.remove(name);
|
onRoomKeyRequestSub.remove(name);
|
||||||
onKeyVerificationRequestSub[name]?.cancel();
|
onKeyVerificationRequestSub[name]?.cancel();
|
||||||
onKeyVerificationRequestSub.remove(name);
|
onKeyVerificationRequestSub.remove(name);
|
||||||
onLoginStateChanged[name]?.cancel();
|
onLogoutSub[name]?.cancel();
|
||||||
onLoginStateChanged.remove(name);
|
onLogoutSub.remove(name);
|
||||||
onNotification[name]?.cancel();
|
onNotification[name]?.cancel();
|
||||||
onNotification.remove(name);
|
onNotification.remove(name);
|
||||||
}
|
}
|
||||||
|
|
@ -373,7 +371,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
|
|
||||||
onRoomKeyRequestSub.values.map((s) => s.cancel());
|
onRoomKeyRequestSub.values.map((s) => s.cancel());
|
||||||
onKeyVerificationRequestSub.values.map((s) => s.cancel());
|
onKeyVerificationRequestSub.values.map((s) => s.cancel());
|
||||||
onLoginStateChanged.values.map((s) => s.cancel());
|
onLogoutSub.values.map((s) => s.cancel());
|
||||||
onNotification.values.map((s) => s.cancel());
|
onNotification.values.map((s) => s.cancel());
|
||||||
client.httpClient.close();
|
client.httpClient.close();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,40 +45,22 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Avatar(
|
||||||
name: displayname,
|
name: displayname,
|
||||||
|
size: 30,
|
||||||
mxContent: user.avatarUrl,
|
mxContent: user.avatarUrl,
|
||||||
presenceUserId: user.id,
|
presenceUserId: user.id,
|
||||||
presenceBackgroundColor: theme.colorScheme.surfaceContainer,
|
presenceBackgroundColor: theme.colorScheme.surfaceContainer,
|
||||||
),
|
),
|
||||||
Column(
|
ConstrainedBox(
|
||||||
mainAxisSize: .min,
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
crossAxisAlignment: .start,
|
child: Text(
|
||||||
children: [
|
displayname,
|
||||||
ConstrainedBox(
|
maxLines: 1,
|
||||||
constraints: const BoxConstraints(maxWidth: 128),
|
overflow: TextOverflow.ellipsis,
|
||||||
child: Text(
|
),
|
||||||
displayname,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: theme.textTheme.labelLarge,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 128),
|
|
||||||
child: Text(
|
|
||||||
user.id,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: const TextStyle(fontSize: 10),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const PopupMenuDivider(),
|
|
||||||
if (onMention != null)
|
if (onMention != null)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _MemberActions.mention,
|
value: _MemberActions.mention,
|
||||||
|
|
|
||||||
24
pubspec.lock
24
pubspec.lock
|
|
@ -165,10 +165,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1112,18 +1112,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.18"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
matrix:
|
matrix:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1877,26 +1877,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.26.3"
|
version: "1.29.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.9"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.12"
|
version: "0.6.15"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue