mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 05:12:11 +00:00
Improved: Onboarding flow for new users.
This commit is contained in:
parent
c0e45cfe1f
commit
2d6a2e436f
15 changed files with 660 additions and 550 deletions
|
|
@ -3,6 +3,7 @@
|
|||
## 0.2.17
|
||||
|
||||
- New: Adds an "Ask a Friend" button to new contact suggestions.
|
||||
- Improved: Onboarding flow for new users.
|
||||
- Improved: The blue verification checkmark now displays the total number of verifications.
|
||||
- Fix: Issue with receiving messages when user closed app while decrypting
|
||||
- Fix: Background message fetching reliability.
|
||||
|
|
|
|||
13
lib/app.dart
13
lib/app.dart
|
|
@ -137,12 +137,14 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
bool _isLoaded = false;
|
||||
bool _isTwonlyLocked = true;
|
||||
bool _wasLogged = true;
|
||||
late int _initialPage;
|
||||
|
||||
(Future<int>?, bool) _proofOfWork = (null, false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initialPage = widget.initialPage;
|
||||
Log.info('AppWidgetState: initState started');
|
||||
initAsync();
|
||||
}
|
||||
|
|
@ -150,6 +152,12 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
Future<void> initAsync() async {
|
||||
Log.info('AppWidgetState: initAsync started');
|
||||
if (userService.isUserCreated) {
|
||||
if (_initialPage != 0) {
|
||||
final count = await twonlyDB.contactsDao.getContactsCount();
|
||||
if (count == 0) {
|
||||
_initialPage = 0;
|
||||
}
|
||||
}
|
||||
try {
|
||||
unawaited(FirebaseMessaging.instance.requestPermission());
|
||||
} catch (e) {
|
||||
|
|
@ -200,8 +208,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
_isTwonlyLocked = false;
|
||||
}),
|
||||
);
|
||||
} else if (!userService.currentUser.skipSetupPages &&
|
||||
userService.currentUser.currentSetupPage != null) {
|
||||
} else if (!userService.currentUser.skipSetupPages && userService.currentUser.currentSetupPage != null) {
|
||||
// This will only be shown in case the user have not skipped
|
||||
child = SetupView(
|
||||
onUpdate: () => setState(() {
|
||||
|
|
@ -210,7 +217,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
);
|
||||
} else {
|
||||
child = HomeView(
|
||||
initialPage: widget.initialPage,
|
||||
initialPage: _initialPage,
|
||||
);
|
||||
}
|
||||
} else if (_showOnboarding) {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,13 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
|||
return select(contacts).get();
|
||||
}
|
||||
|
||||
Future<int> getContactsCount() async {
|
||||
final count = contacts.userId.count();
|
||||
final query = selectOnly(contacts)..addColumns([count]);
|
||||
final result = await query.map((row) => row.read(count)).getSingle();
|
||||
return result ?? 0;
|
||||
}
|
||||
|
||||
Stream<int?> watchContactsBlocked() {
|
||||
final count = contacts.userId.count();
|
||||
final query = selectOnly(contacts)
|
||||
|
|
|
|||
56
lib/src/visual/components/contact_request_badge.comp.dart
Normal file
56
lib/src/visual/components/contact_request_badge.comp.dart
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class ContactRequestBadgeComp extends StatelessWidget {
|
||||
const ContactRequestBadgeComp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<int?>(
|
||||
stream: twonlyDB.contactsDao.watchContactsRequestedCount(),
|
||||
builder: (context, snapshot) {
|
||||
final count = snapshot.data ?? 0;
|
||||
if (count == 0) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: NotificationBadgeComp(
|
||||
backgroundColor: isDarkMode(context) ? Colors.white : Colors.black,
|
||||
textColor: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
count: count.toString(),
|
||||
child: IconButton(
|
||||
color: Colors.black,
|
||||
icon: const FaIcon(
|
||||
FontAwesomeIcons.userPlus,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,53 +44,52 @@ class _ProfileQrCodeCompState extends State<ProfileQrCodeComp> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading || _qrCode == null) {
|
||||
return SizedBox(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
// padding: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 3,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: QrImageView.withQr(
|
||||
qr: QrCode.fromData(
|
||||
data: _qrCode!,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.M,
|
||||
),
|
||||
eyeStyle: QrEyeStyle(
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
borderRadius: 2,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
borderRadius: 2,
|
||||
),
|
||||
gapless: false,
|
||||
embeddedImage: (widget.showAvatar && _userAvatar != null)
|
||||
? MemoryImage(_userAvatar!)
|
||||
: null,
|
||||
embeddedImageStyle: QrEmbeddedImageStyle(
|
||||
size: const Size(60, 66),
|
||||
embeddedImageShape: EmbeddedImageShape.square,
|
||||
shapeColor: context.color.primary,
|
||||
safeArea: true,
|
||||
),
|
||||
size: widget.size,
|
||||
final loaded = !_isLoading && _qrCode != null;
|
||||
return SizedBox(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: loaded
|
||||
? Container(
|
||||
key: const ValueKey('qr_code_container'),
|
||||
// padding: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 3,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: QrImageView.withQr(
|
||||
qr: QrCode.fromData(
|
||||
data: _qrCode!,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.M,
|
||||
),
|
||||
eyeStyle: QrEyeStyle(
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
borderRadius: 2,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
borderRadius: 2,
|
||||
),
|
||||
gapless: false,
|
||||
embeddedImage: (widget.showAvatar && _userAvatar != null) ? MemoryImage(_userAvatar!) : null,
|
||||
embeddedImageStyle: QrEmbeddedImageStyle(
|
||||
size: const Size(60, 66),
|
||||
embeddedImageShape: EmbeddedImageShape.square,
|
||||
shapeColor: context.color.primary,
|
||||
safeArea: true,
|
||||
),
|
||||
size: widget.size,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(key: ValueKey('qr_code_placeholder')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ import 'dart:collection';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
|
@ -15,6 +13,7 @@ import 'package:twonly/src/services/flame.service.dart';
|
|||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_request_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
||||
import 'package:twonly/src/visual/elements/headline.element.dart';
|
||||
|
|
@ -22,6 +21,7 @@ import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
|||
import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/best_friends_selector.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/shortcut_row.comp.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/background.layer.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/empty_chat_list.comp.dart';
|
||||
|
||||
class ShareImageView extends StatefulWidget {
|
||||
const ShareImageView({
|
||||
|
|
@ -111,9 +111,7 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
|
||||
for (final group in groups) {
|
||||
if (group.pinned) continue;
|
||||
if (!group.archived &&
|
||||
getFlameCounterFromGroup(group).counter > 0 &&
|
||||
bestFriends.length < 6) {
|
||||
if (!group.archived && getFlameCounterFromGroup(group).counter > 0 && bestFriends.length < 6) {
|
||||
bestFriends.add(group);
|
||||
} else {
|
||||
otherUsers.add(group);
|
||||
|
|
@ -133,10 +131,7 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
await updateGroups(
|
||||
_allGroups
|
||||
.where(
|
||||
(x) =>
|
||||
!x.archived ||
|
||||
!hideArchivedUsers ||
|
||||
widget.selectedGroupIds.contains(x.groupId),
|
||||
(x) => !x.archived || !hideArchivedUsers || widget.selectedGroupIds.contains(x.groupId),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
|
@ -160,31 +155,23 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.shareImageTitle),
|
||||
actions: const [
|
||||
ContactRequestBadgeComp(),
|
||||
SizedBox(width: 15),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 40,
|
||||
left: 10,
|
||||
top: 20,
|
||||
right: 10,
|
||||
),
|
||||
child: Column(
|
||||
child: ListView(
|
||||
children: [
|
||||
if (_allGroups.isEmpty)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: FilledButton.icon(
|
||||
icon: const Icon(Icons.person_add),
|
||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||
label: Text(
|
||||
context.lang.chatListViewSearchUserNameBtn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (_allGroups.isNotEmpty)
|
||||
const EmptyChatListComp()
|
||||
else ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: TextField(
|
||||
|
|
@ -195,163 +182,161 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ShortcutRowComp(
|
||||
selectedGroupIds: widget.selectedGroupIds,
|
||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||
),
|
||||
if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10),
|
||||
BestFriendsSelector(
|
||||
groups: _pinnedContacts,
|
||||
selectedGroupIds: widget.selectedGroupIds,
|
||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||
title: context.lang.shareImagePinnedContacts,
|
||||
showSelectAll:
|
||||
!widget.mediaFileService.mediaFile.requiresAuthentication,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BestFriendsSelector(
|
||||
groups: _bestFriends,
|
||||
selectedGroupIds: widget.selectedGroupIds,
|
||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||
title: context.lang.shareImageBestFriends,
|
||||
showSelectAll:
|
||||
!widget.mediaFileService.mediaFile.requiresAuthentication,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_otherUsers.isNotEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
HeadLineComp(context.lang.shareImageAllUsers),
|
||||
if (_allGroups.any((x) => x.archived))
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
context.lang.shareImageShowArchived,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Checkbox(
|
||||
value: !hideArchivedUsers,
|
||||
side: WidgetStateBorderSide.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const BorderSide(width: 0);
|
||||
}
|
||||
return BorderSide(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.outline,
|
||||
);
|
||||
const SizedBox(height: 10),
|
||||
ShortcutRowComp(
|
||||
selectedGroupIds: widget.selectedGroupIds,
|
||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||
),
|
||||
if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10),
|
||||
BestFriendsSelector(
|
||||
groups: _pinnedContacts,
|
||||
selectedGroupIds: widget.selectedGroupIds,
|
||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||
title: context.lang.shareImagePinnedContacts,
|
||||
showSelectAll: !widget.mediaFileService.mediaFile.requiresAuthentication,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
BestFriendsSelector(
|
||||
groups: _bestFriends,
|
||||
selectedGroupIds: widget.selectedGroupIds,
|
||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||
title: context.lang.shareImageBestFriends,
|
||||
showSelectAll: !widget.mediaFileService.mediaFile.requiresAuthentication,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (_otherUsers.isNotEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
HeadLineComp(context.lang.shareImageAllUsers),
|
||||
if (_allGroups.any((x) => x.archived))
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
context.lang.shareImageShowArchived,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.75,
|
||||
child: Checkbox(
|
||||
value: !hideArchivedUsers,
|
||||
side: WidgetStateBorderSide.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const BorderSide(width: 0);
|
||||
}
|
||||
return BorderSide(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.outline,
|
||||
);
|
||||
},
|
||||
),
|
||||
onChanged: (a) async {
|
||||
hideArchivedUsers = !hideArchivedUsers;
|
||||
await _filterUsers(lastQuery);
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
),
|
||||
onChanged: (a) async {
|
||||
hideArchivedUsers = !hideArchivedUsers;
|
||||
await _filterUsers(lastQuery);
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_otherUsers.isNotEmpty)
|
||||
Expanded(
|
||||
child: UserList(
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_otherUsers.isNotEmpty)
|
||||
UserList(
|
||||
List.from(_otherUsers),
|
||||
selectedGroupIds: widget.selectedGroupIds,
|
||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: SizedBox(
|
||||
height: 168,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20, right: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (widget.mediaFileService.mediaFile.type == MediaType.image &&
|
||||
_screenshotImage?.image != null &&
|
||||
userService.currentUser.showShowImagePreviewWhenSending)
|
||||
SizedBox(
|
||||
height: 100,
|
||||
width: 100 * 9 / 16,
|
||||
child: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.color.primary,
|
||||
width: 2,
|
||||
),
|
||||
color: context.color.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: CustomPaint(
|
||||
painter: UiImagePainter(_screenshotImage!.image!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FilledButton.icon(
|
||||
icon: !mediaStoreFutureReady || sendingImage
|
||||
? SizedBox(
|
||||
height: 12,
|
||||
width: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Theme.of(context).colorScheme.inversePrimary,
|
||||
floatingActionButton: _allGroups.isEmpty
|
||||
? null
|
||||
: SizedBox(
|
||||
height: 168,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20, right: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (widget.mediaFileService.mediaFile.type == MediaType.image &&
|
||||
_screenshotImage?.image != null &&
|
||||
userService.currentUser.showShowImagePreviewWhenSending)
|
||||
SizedBox(
|
||||
height: 100,
|
||||
width: 100 * 9 / 16,
|
||||
child: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.color.primary,
|
||||
width: 2,
|
||||
),
|
||||
color: context.color.primary,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: CustomPaint(
|
||||
painter: UiImagePainter(_screenshotImage!.image!),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||
onPressed: () async {
|
||||
if (!mediaStoreFutureReady ||
|
||||
widget.selectedGroupIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
),
|
||||
FilledButton.icon(
|
||||
icon: !mediaStoreFutureReady || sendingImage
|
||||
? SizedBox(
|
||||
height: 12,
|
||||
width: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Theme.of(context).colorScheme.inversePrimary,
|
||||
),
|
||||
)
|
||||
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||
onPressed: () async {
|
||||
if (!mediaStoreFutureReady || widget.selectedGroupIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
sendingImage = true;
|
||||
});
|
||||
setState(() {
|
||||
sendingImage = true;
|
||||
});
|
||||
|
||||
// in case mediaStoreFutureReady is ready, the image is stored in the originalPath
|
||||
await insertMediaFileInMessagesTable(
|
||||
widget.mediaFileService,
|
||||
widget.selectedGroupIds.toList(),
|
||||
additionalData: widget.additionalData,
|
||||
);
|
||||
// in case mediaStoreFutureReady is ready, the image is stored in the originalPath
|
||||
await insertMediaFileInMessagesTable(
|
||||
widget.mediaFileService,
|
||||
widget.selectedGroupIds.toList(),
|
||||
additionalData: widget.additionalData,
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
const EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
!mediaStoreFutureReady || widget.selectedGroupIds.isEmpty
|
||||
? context.color.onSurface
|
||||
: context.color.primary,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
'${context.lang.shareImagedEditorSendImage} (${widget.selectedGroupIds.length})',
|
||||
style: const TextStyle(fontSize: 17),
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
const EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
!mediaStoreFutureReady || widget.selectedGroupIds.isEmpty
|
||||
? context.color.onSurface
|
||||
: context.color.primary,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
'${context.lang.shareImagedEditorSendImage} (${widget.selectedGroupIds.length})',
|
||||
style: const TextStyle(fontSize: 17),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -375,6 +360,8 @@ class UserList extends StatelessWidget {
|
|||
);
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
restorationId: 'new_message_users_list',
|
||||
itemCount: groups.length,
|
||||
itemBuilder: (context, i) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
|||
import 'package:twonly/src/visual/components/connection_status.comp.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/empty_chat_list.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/feedback_btn.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_list_components/group_list_item.comp.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup/components/finish_setup.comp.dart';
|
||||
|
|
@ -31,11 +32,15 @@ class ChatListView extends StatefulWidget {
|
|||
|
||||
class _ChatListViewState extends State<ChatListView> {
|
||||
StreamSubscription<void>? _userSub;
|
||||
late StreamSubscription<List<Group>> _contactsSub;
|
||||
StreamSubscription<List<Group>>? _contactsSub;
|
||||
StreamSubscription<List<Contact>>? _contactsCountSub;
|
||||
List<Group> _groupsNotPinned = [];
|
||||
List<Group> _groupsPinned = [];
|
||||
List<Group> _groupsArchived = [];
|
||||
|
||||
bool _hasContacts = true;
|
||||
bool get _hasOpenGroup => _groupsNotPinned.isNotEmpty || _groupsArchived.isNotEmpty || _groupsPinned.isNotEmpty;
|
||||
|
||||
GlobalKey searchForOtherUsers = GlobalKey();
|
||||
bool showFeedbackShortcut = false;
|
||||
|
||||
|
|
@ -58,33 +63,34 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
_contactsSub = stream.listen((groups) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_groupsNotPinned = groups
|
||||
.where((x) => !x.pinned && !x.archived)
|
||||
.toList();
|
||||
_groupsNotPinned = groups.where((x) => !x.pinned && !x.archived).toList();
|
||||
_groupsPinned = groups.where((x) => x.pinned && !x.archived).toList();
|
||||
_groupsArchived = groups.where((x) => x.archived).toList();
|
||||
});
|
||||
});
|
||||
|
||||
_countContactRequestStream = twonlyDB.contactsDao
|
||||
.watchContactsRequestedCount()
|
||||
.listen((update) {
|
||||
if (update != null) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_countContactRequest = update;
|
||||
});
|
||||
}
|
||||
});
|
||||
_contactsCountSub = twonlyDB.contactsDao.watchAllAcceptedContacts().listen((contacts) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_hasContacts = contacts.isNotEmpty;
|
||||
});
|
||||
});
|
||||
|
||||
_countAnnouncedStream = twonlyDB.userDiscoveryDao
|
||||
.watchNewAnnouncementsWithDataCount()
|
||||
.listen((update) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_countAnnouncedUsers = update;
|
||||
});
|
||||
_countContactRequestStream = twonlyDB.contactsDao.watchContactsRequestedCount().listen((update) {
|
||||
if (update != null) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_countContactRequest = update;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_countAnnouncedStream = twonlyDB.userDiscoveryDao.watchNewAnnouncementsWithDataCount().listen((update) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_countAnnouncedUsers = update;
|
||||
});
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final changeLog = await rootBundle.loadString('CHANGELOG.md');
|
||||
|
|
@ -93,8 +99,7 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
changeLog.codeUnits,
|
||||
)).bytes;
|
||||
if (!userService.currentUser.hideChangeLog &&
|
||||
userService.currentUser.lastChangeLogHash.toString() !=
|
||||
changeLogHash.toString()) {
|
||||
userService.currentUser.lastChangeLogHash.toString() != changeLogHash.toString()) {
|
||||
await UserService.update((u) {
|
||||
u.lastChangeLogHash = changeLogHash;
|
||||
});
|
||||
|
|
@ -113,7 +118,8 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_contactsSub.cancel();
|
||||
_contactsSub?.cancel();
|
||||
_contactsCountSub?.cancel();
|
||||
_countContactRequestStream.cancel();
|
||||
_countAnnouncedStream.cancel();
|
||||
_userSub?.cancel();
|
||||
|
|
@ -182,16 +188,11 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
),
|
||||
Center(
|
||||
child: NotificationBadgeComp(
|
||||
backgroundColor: isDarkMode(context)
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
backgroundColor: isDarkMode(context) ? Colors.white : Colors.black,
|
||||
textColor: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
count: (_countAnnouncedUsers + _countContactRequest)
|
||||
.toString(),
|
||||
count: (_countAnnouncedUsers + _countContactRequest).toString(),
|
||||
child: IconButton(
|
||||
color: (_countAnnouncedUsers + _countContactRequest > 0)
|
||||
? Colors.black
|
||||
: null,
|
||||
color: (_countAnnouncedUsers + _countContactRequest > 0) ? Colors.black : null,
|
||||
key: searchForOtherUsers,
|
||||
icon: const FaIcon(FontAwesomeIcons.userPlus, size: 18),
|
||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||
|
|
@ -217,21 +218,11 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
children: [
|
||||
const FinishSetupComp(),
|
||||
const MissingBackupComp(),
|
||||
if (_groupsNotPinned.isEmpty &&
|
||||
_groupsPinned.isEmpty &&
|
||||
_groupsArchived.isEmpty)
|
||||
if (!_hasOpenGroup)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: FilledButton.icon(
|
||||
icon: const Icon(Icons.person_add),
|
||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||
label: Text(
|
||||
context.lang.chatListViewSearchUserNameBtn,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: const [EmptyChatListComp()],
|
||||
),
|
||||
)
|
||||
else
|
||||
|
|
@ -243,10 +234,7 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
_groupsNotPinned.length +
|
||||
(_groupsArchived.isNotEmpty ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >=
|
||||
_groupsNotPinned.length +
|
||||
_groupsPinned.length +
|
||||
(_groupsPinned.isNotEmpty ? 1 : 0)) {
|
||||
if (index >= _groupsNotPinned.length + _groupsPinned.length + (_groupsPinned.isNotEmpty ? 1 : 0)) {
|
||||
if (_groupsArchived.isEmpty) return Container();
|
||||
return ListTile(
|
||||
title: Text(
|
||||
|
|
@ -289,42 +277,44 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Material(
|
||||
elevation: 3,
|
||||
shape: const CircleBorder(),
|
||||
color: context.color.primary,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => context.push(Routes.settingsPublicProfile),
|
||||
child: SizedBox(
|
||||
width: 45,
|
||||
height: 45,
|
||||
child: Center(
|
||||
floatingActionButton: !_hasContacts
|
||||
? null
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Material(
|
||||
elevation: 3,
|
||||
shape: const CircleBorder(),
|
||||
color: context.color.primary,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => context.push(Routes.settingsPublicProfile),
|
||||
child: SizedBox(
|
||||
width: 45,
|
||||
height: 45,
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.qrcode,
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FloatingActionButton(
|
||||
backgroundColor: context.color.primary,
|
||||
onPressed: () => context.push(Routes.chatsStartNewChat),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.qrcode,
|
||||
FontAwesomeIcons.penToSquare,
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FloatingActionButton(
|
||||
backgroundColor: context.color.primary,
|
||||
onPressed: () => context.push(Routes.chatsStartNewChat),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.penToSquare,
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart' show FaIcon, FontAwesomeIcons;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class EmptyChatListComp extends StatelessWidget {
|
||||
const EmptyChatListComp({super.key});
|
||||
|
||||
Future<void> _shareProfile(BuildContext context) async {
|
||||
try {
|
||||
final pubKey = await getUserPublicKey();
|
||||
final params = ShareParams(
|
||||
text: 'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(pubKey)}',
|
||||
);
|
||||
await SharePlus.instance.share(params);
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
await context.push(Routes.chatsAddNewUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
width: double.infinity,
|
||||
),
|
||||
const Text(
|
||||
'Find your first friend',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Let friends scan your QR code, or share them your profile.',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: context.color.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
const Center(child: ProfileQrCodeComp()),
|
||||
const SizedBox(height: 36),
|
||||
// 3. Action Buttons
|
||||
// Button 1: Share Profile (Full Width)
|
||||
FilledButton.icon(
|
||||
style: primaryColorButtonStyle,
|
||||
onPressed: () => _shareProfile(context),
|
||||
icon: const FaIcon(FontAwesomeIcons.shareNodes, size: 20),
|
||||
label: const Text(
|
||||
'Share your profile',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Button Row: Scan QR Code & Enter Username
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: secondaryGreyButtonStyle(context),
|
||||
onPressed: () => context.push(Routes.cameraQRScanner),
|
||||
icon: const Icon(Icons.qr_code_scanner_rounded, size: 20),
|
||||
label: const FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'Scan QR Code',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
style: secondaryGreyButtonStyle(context),
|
||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||
icon: const Icon(Icons.person_add_rounded, size: 20),
|
||||
label: const FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'Add by Username',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -62,24 +62,21 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
}
|
||||
},
|
||||
);
|
||||
_newAnnouncedUsersStream = twonlyDB.userDiscoveryDao
|
||||
.watchNewAnnouncedUsersWithRelations()
|
||||
.listen((update) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_newAnnouncedUsers = update;
|
||||
});
|
||||
}
|
||||
|
||||
_newAnnouncedUsersStream = twonlyDB.userDiscoveryDao.watchNewAnnouncedUsersWithRelations().listen((update) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_newAnnouncedUsers = update;
|
||||
});
|
||||
_allAnnouncedUsersStream = twonlyDB.userDiscoveryDao
|
||||
.watchAllAnnouncedUsersWithRelations()
|
||||
.listen((update) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_allAnnouncedUsers = update;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
_allAnnouncedUsersStream = twonlyDB.userDiscoveryDao.watchAllAnnouncedUsersWithRelations().listen((update) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_allAnnouncedUsers = update;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (widget.username != null) {
|
||||
_usernameController.text = widget.username!;
|
||||
|
|
@ -93,8 +90,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
Future<void> _shareProfile() async {
|
||||
final pubKey = await getUserPublicKey();
|
||||
final params = ShareParams(
|
||||
text:
|
||||
'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(pubKey)}',
|
||||
text: 'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(pubKey)}',
|
||||
);
|
||||
await SharePlus.instance.share(params);
|
||||
}
|
||||
|
|
@ -194,9 +190,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
),
|
||||
);
|
||||
|
||||
if (widget.publicKey != null &&
|
||||
mounted &&
|
||||
widget.publicKey!.equals(userdata.publicIdentityKey)) {
|
||||
if (widget.publicKey != null && mounted && widget.publicKey!.equals(userdata.publicIdentityKey)) {
|
||||
final markAsVerified = await showAlertDialog(
|
||||
context,
|
||||
context.lang.linkFromUsername(username),
|
||||
|
|
@ -321,15 +315,9 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
FontAwesomeIcons.shareNodes,
|
||||
size: 14,
|
||||
),
|
||||
label: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
context.lang.shareYourProfile,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
context.lang.shareYourProfile,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -353,15 +341,9 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
FontAwesomeIcons.qrcode,
|
||||
size: 14,
|
||||
),
|
||||
label: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
context.lang.openYourOwnQRcode,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
context.lang.openYourOwnQRcode,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -371,11 +353,18 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
OpenRequestsListComp(
|
||||
contacts: _openRequestsContacts,
|
||||
relations: _allAnnouncedUsers,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
OpenRequestsListComp(
|
||||
contacts: _openRequestsContacts,
|
||||
relations: _allAnnouncedUsers,
|
||||
),
|
||||
FriendSuggestionsComp(_newAnnouncedUsers),
|
||||
],
|
||||
),
|
||||
),
|
||||
FriendSuggestionsComp(_newAnnouncedUsers),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
|||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/headline.element.dart';
|
||||
|
|
@ -63,8 +64,17 @@ class OpenRequestsListComp extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
const update = ContactsCompanion(blocked: Value(true));
|
||||
await twonlyDB.contactsDao.updateContact(contact.userId, update);
|
||||
final block = await showAlertDialog(
|
||||
context,
|
||||
context.lang.contactBlockTitle(getContactDisplayName(contact)),
|
||||
context.lang.contactBlockBody,
|
||||
);
|
||||
if (block) {
|
||||
const update = ContactsCompanion(blocked: Value(true));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.contactsDao.updateContact(contact.userId, update);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
@ -179,9 +189,7 @@ class OpenRequestsListComp extends StatelessWidget {
|
|||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: contact.requested
|
||||
? requestedActions(context, contact)
|
||||
: sendRequestActions(context, contact),
|
||||
children: contact.requested ? requestedActions(context, contact) : sendRequestActions(context, contact),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -78,9 +78,7 @@ class HomeViewState extends State<HomeView> {
|
|||
_selectNotificationSub = selectNotificationStream.stream.listen((
|
||||
response,
|
||||
) async {
|
||||
if (response.payload != null &&
|
||||
response.payload!.startsWith(Routes.chats) &&
|
||||
response.payload! != Routes.chats) {
|
||||
if (response.payload != null && response.payload!.startsWith(Routes.chats) && response.payload! != Routes.chats) {
|
||||
await routerProvider.push(response.payload!);
|
||||
}
|
||||
streamHomeViewPageIndex.add(0);
|
||||
|
|
@ -116,40 +114,31 @@ class HomeViewState extends State<HomeView> {
|
|||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.initialPage == 1 &&
|
||||
!userService.currentUser.startWithCameraOpen ||
|
||||
widget.initialPage == 0) {
|
||||
if (widget.initialPage == 1 && !userService.currentUser.startWithCameraOpen || widget.initialPage == 0) {
|
||||
streamHomeViewPageIndex.add(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initAsync() async {
|
||||
final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin
|
||||
.getNotificationAppLaunchDetails();
|
||||
final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||
|
||||
RemoteMessage? initialRemoteMessage;
|
||||
try {
|
||||
initialRemoteMessage = await FirebaseMessaging.instance
|
||||
.getInitialMessage();
|
||||
initialRemoteMessage = await FirebaseMessaging.instance.getInitialMessage();
|
||||
} catch (e) {
|
||||
Log.error('Could not get initial Firebase message: $e');
|
||||
}
|
||||
|
||||
if (widget.initialPage == 0 ||
|
||||
initialRemoteMessage != null ||
|
||||
(notificationAppLaunchDetails != null &&
|
||||
notificationAppLaunchDetails.didNotificationLaunchApp)) {
|
||||
(notificationAppLaunchDetails != null && notificationAppLaunchDetails.didNotificationLaunchApp)) {
|
||||
if (initialRemoteMessage != null) {
|
||||
Log.info('App launched from iOS/Remote push notification tap.');
|
||||
streamHomeViewPageIndex.add(0);
|
||||
} else if (notificationAppLaunchDetails?.didNotificationLaunchApp ??
|
||||
false) {
|
||||
final payload =
|
||||
notificationAppLaunchDetails?.notificationResponse?.payload;
|
||||
if (payload != null &&
|
||||
payload.startsWith(Routes.chats) &&
|
||||
payload != Routes.chats) {
|
||||
} else if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
|
||||
final payload = notificationAppLaunchDetails?.notificationResponse?.payload;
|
||||
if (payload != null && payload.startsWith(Routes.chats) && payload != Routes.chats) {
|
||||
await routerProvider.push(payload);
|
||||
streamHomeViewPageIndex.add(0);
|
||||
}
|
||||
|
|
@ -190,25 +179,30 @@ class HomeViewState extends State<HomeView> {
|
|||
_disableCameraTimer?.cancel();
|
||||
|
||||
if (notification.depth > 0 && notification.metrics.axis == Axis.vertical) {
|
||||
if (_activePageIdx == 2 &&
|
||||
notification.metrics.pixels < 100 &&
|
||||
!_isBottomNavVisible) {
|
||||
setState(() {
|
||||
_isBottomNavVisible = true;
|
||||
});
|
||||
} else if (notification is ScrollUpdateNotification) {
|
||||
final delta = notification.scrollDelta ?? 0;
|
||||
if (delta > 5 &&
|
||||
_isBottomNavVisible &&
|
||||
(_activePageIdx != 2 || notification.metrics.pixels >= 100)) {
|
||||
setState(() {
|
||||
_isBottomNavVisible = false;
|
||||
});
|
||||
} else if (delta < -5 && !_isBottomNavVisible) {
|
||||
final canScroll = notification.metrics.maxScrollExtent > notification.metrics.minScrollExtent;
|
||||
if (!canScroll) {
|
||||
if (!_isBottomNavVisible) {
|
||||
setState(() {
|
||||
_isBottomNavVisible = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (_activePageIdx == 2 && notification.metrics.pixels < 100 && !_isBottomNavVisible) {
|
||||
setState(() {
|
||||
_isBottomNavVisible = true;
|
||||
});
|
||||
} else if (notification is ScrollUpdateNotification) {
|
||||
final delta = notification.scrollDelta ?? 0;
|
||||
if (delta > 5 && _isBottomNavVisible && (_activePageIdx != 2 || notification.metrics.pixels >= 100)) {
|
||||
setState(() {
|
||||
_isBottomNavVisible = false;
|
||||
});
|
||||
} else if (delta < -5 && !_isBottomNavVisible) {
|
||||
setState(() {
|
||||
_isBottomNavVisible = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,9 +238,7 @@ class HomeViewState extends State<HomeView> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: GestureDetector(
|
||||
onDoubleTap: _offsetRatio == 0
|
||||
? _mainCameraController.onDoubleTap
|
||||
: null,
|
||||
onDoubleTap: _offsetRatio == 0 ? _mainCameraController.onDoubleTap : null,
|
||||
onTapDown: _offsetRatio == 0 ? _mainCameraController.onTapDown : null,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
|
|
@ -281,16 +273,12 @@ class HomeViewState extends State<HomeView> {
|
|||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: (_offsetRatio > 0.25)
|
||||
? MediaQuery.sizeOf(context).height * 2
|
||||
: 0,
|
||||
bottom: (_offsetRatio > 0.25) ? MediaQuery.sizeOf(context).height * 2 : 0,
|
||||
child: Opacity(
|
||||
opacity: 1 - (_offsetRatio * 4) % 1,
|
||||
child: CameraPreviewControllerView(
|
||||
mainController: _mainCameraController,
|
||||
isVisible:
|
||||
((1 - (_offsetRatio * 4) % 1) == 1) &&
|
||||
_activePageIdx == 1,
|
||||
isVisible: ((1 - (_offsetRatio * 4) % 1) == 1) && _activePageIdx == 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -153,21 +153,19 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
final isDark = isDarkMode(context);
|
||||
final cardColor = isDark ? const Color(0xFF1E293B) : Colors.white;
|
||||
final inputColor = isDark ? const Color(0xFF0F172A) : Colors.grey[100];
|
||||
final sloganColor = isDark
|
||||
? Colors.white.withValues(alpha: 0.9)
|
||||
: Colors.grey[800];
|
||||
final sloganColor = isDark ? Colors.white.withValues(alpha: 0.9) : Colors.grey[800];
|
||||
final secondaryButtonColor = isDark ? Colors.grey[400] : Colors.grey[600];
|
||||
|
||||
return OnboardingWrapper(
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: 30),
|
||||
Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: const LinkLogoAnimation(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
|
|
@ -180,7 +178,7 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
const SizedBox(height: 30),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
|
|
@ -188,9 +186,7 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
borderRadius: BorderRadius.circular(32),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: isDark
|
||||
? Colors.black.withValues(alpha: 0.3)
|
||||
: Colors.black.withValues(alpha: 0.1),
|
||||
color: isDark ? Colors.black.withValues(alpha: 0.3) : Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
|
|
@ -262,8 +258,7 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (_showUserNameError &&
|
||||
usernameController.text.length < 3) ...[
|
||||
if (_showUserNameError && usernameController.text.length < 3) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.lang.registerUsernameLimits,
|
||||
|
|
|
|||
|
|
@ -10,58 +10,64 @@ class MockContactRequestActionsComp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: SizedBox(
|
||||
// width: 125,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 2, left: 4),
|
||||
backgroundColor: context.color.surfaceContainerHigh,
|
||||
foregroundColor: context.color.onSurface,
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.person_off_rounded,
|
||||
color: Color.fromARGB(164, 244, 67, 54),
|
||||
size: 12,
|
||||
),
|
||||
Text(
|
||||
context.lang.contactActionBlock,
|
||||
style: const TextStyle(fontSize: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 2, left: 4),
|
||||
backgroundColor: context.color.surfaceContainerHigh,
|
||||
foregroundColor: context.color.onSurface,
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.check, color: Colors.green, size: 12),
|
||||
Text(
|
||||
context.lang.contactActionAccept,
|
||||
style: const TextStyle(fontSize: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
// width: 45,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 2, left: 4),
|
||||
backgroundColor: context.color.surfaceContainerHigh,
|
||||
foregroundColor: context.color.onSurface,
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.person_off_rounded,
|
||||
color: Color.fromARGB(164, 244, 67, 54),
|
||||
size: 12,
|
||||
),
|
||||
Text(
|
||||
context.lang.contactActionBlock,
|
||||
style: const TextStyle(fontSize: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
// width: 50,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 2, left: 4),
|
||||
backgroundColor: context.color.surfaceContainerHigh,
|
||||
foregroundColor: context.color.onSurface,
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.check, color: Colors.green, size: 12),
|
||||
Text(
|
||||
context.lang.contactActionAccept,
|
||||
style: const TextStyle(fontSize: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
|
|
@ -86,28 +92,59 @@ class MockContactSuggestedActionsComp extends StatelessWidget {
|
|||
return IgnorePointer(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 8, left: 4),
|
||||
).merge(secondaryGreyButtonStyle(context)),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: FaIcon(FontAwesomeIcons.userPlus, size: 10),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 8, left: 4),
|
||||
).merge(secondaryGreyButtonStyle(context)),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: FaIcon(FontAwesomeIcons.circleQuestion, size: 10),
|
||||
),
|
||||
Text(
|
||||
context.lang.friendSuggestionsAskFriend,
|
||||
style: const TextStyle(fontSize: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
context.lang.friendSuggestionsRequest,
|
||||
style: const TextStyle(fontSize: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 8, left: 4),
|
||||
).merge(secondaryGreyButtonStyle(context)),
|
||||
onPressed: () {},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: FaIcon(FontAwesomeIcons.userPlus, size: 10),
|
||||
),
|
||||
Text(
|
||||
context.lang.friendSuggestionsRequest,
|
||||
style: const TextStyle(fontSize: 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/components/contact_request_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/better_list_title.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class PublicProfileView extends StatefulWidget {
|
||||
const PublicProfileView({super.key});
|
||||
|
|
@ -24,8 +23,6 @@ class PublicProfileView extends StatefulWidget {
|
|||
|
||||
class _PublicProfileViewState extends State<PublicProfileView> {
|
||||
Uint8List? _publicKey;
|
||||
int _countContactRequest = 0;
|
||||
late StreamSubscription<int?> _countContactRequestStream;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -36,70 +33,15 @@ class _PublicProfileViewState extends State<PublicProfileView> {
|
|||
Future<void> initAsync() async {
|
||||
_publicKey = await getUserPublicKey();
|
||||
if (mounted) setState(() {});
|
||||
|
||||
_countContactRequestStream = twonlyDB.contactsDao
|
||||
.watchContactsRequestedCount()
|
||||
.listen((update) {
|
||||
if (update != null) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_countContactRequest = update;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_countContactRequestStream.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
actions: [
|
||||
Stack(
|
||||
children: (_countContactRequest == 0)
|
||||
? []
|
||||
: [
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: NotificationBadgeComp(
|
||||
backgroundColor: isDarkMode(context)
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
textColor: isDarkMode(context)
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
count: (_countContactRequest).toString(),
|
||||
child: IconButton(
|
||||
color: (_countContactRequest > 0)
|
||||
? Colors.black
|
||||
: null,
|
||||
icon: const FaIcon(
|
||||
FontAwesomeIcons.userPlus,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
actions: const [
|
||||
ContactRequestBadgeComp(),
|
||||
SizedBox(width: 15),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
|
|
@ -155,8 +97,7 @@ class _PublicProfileViewState extends State<PublicProfileView> {
|
|||
),
|
||||
onTap: () {
|
||||
final params = ShareParams(
|
||||
text:
|
||||
'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(_publicKey!)}',
|
||||
text: 'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(_publicKey!)}',
|
||||
);
|
||||
SharePlus.instance.share(params);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -104,8 +104,7 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final showShareYourFriends =
|
||||
showOnlySpecificPage == UserDiscoveryPages.all ||
|
||||
showOnlySpecificPage == UserDiscoveryPages.shareYourFriends;
|
||||
showOnlySpecificPage == UserDiscoveryPages.all || showOnlySpecificPage == UserDiscoveryPages.shareYourFriends;
|
||||
final showLetYourFriendsFindYou =
|
||||
showOnlySpecificPage == UserDiscoveryPages.all ||
|
||||
showOnlySpecificPage == UserDiscoveryPages.letYourFriendsFindYou;
|
||||
|
|
@ -172,7 +171,6 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
|
|
@ -334,9 +332,7 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
context
|
||||
.lang
|
||||
.userDiscoverySettingsManualApprovalDesc,
|
||||
context.lang.userDiscoverySettingsManualApprovalDesc,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: context.color.onSurfaceVariant,
|
||||
|
|
@ -350,16 +346,13 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
crossFadeState: state.isUserDiscoveryEnabled
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
crossFadeState: state.isUserDiscoveryEnabled ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (showOnlySpecificPage == UserDiscoveryPages.all)
|
||||
const SizedBox(height: 48),
|
||||
if (showOnlySpecificPage == UserDiscoveryPages.all) const SizedBox(height: 48),
|
||||
],
|
||||
if (showLetYourFriendsFindYou) ...[
|
||||
Text(
|
||||
|
|
@ -418,7 +411,6 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
|
|
@ -483,7 +475,6 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 320),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
|
|
@ -592,9 +583,7 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
context
|
||||
.lang
|
||||
.userDiscoverySettingsMutualFriends,
|
||||
context.lang.userDiscoverySettingsMutualFriends,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
|
@ -610,10 +599,9 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
color: context.color.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: context.color.outlineVariant
|
||||
.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
color: context.color.outlineVariant.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
|
|
@ -648,9 +636,7 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
crossFadeState: state.sharePromotion
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
crossFadeState: state.sharePromotion ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue