♻️ Better image loading animation and more commonly used blurhash
This commit is contained in:
@@ -74,7 +74,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (user.value?.profile.background?.id != null)
|
if (user.value?.profile.background != null)
|
||||||
Stack(
|
Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
@@ -112,7 +112,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final hasBackground =
|
final hasBackground =
|
||||||
user.value?.profile.background?.id != null;
|
user.value?.profile.background != null;
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
spacing: hasBackground ? 0 : 16,
|
spacing: hasBackground ? 0 : 16,
|
||||||
|
|||||||
@@ -74,14 +74,10 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final cloudFile =
|
final cloudFile = await FileUploader.createCloudFile(
|
||||||
await FileUploader.createCloudFile(
|
ref: ref,
|
||||||
ref: ref,
|
fileData: UniversalFile(data: result, type: UniversalFileType.image),
|
||||||
fileData: UniversalFile(
|
).future;
|
||||||
data: result,
|
|
||||||
type: UniversalFileType.image,
|
|
||||||
),
|
|
||||||
).future;
|
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
}
|
}
|
||||||
@@ -188,8 +184,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
if (usernameColorType.value == 'gradient') ...{
|
if (usernameColorType.value == 'gradient') ...{
|
||||||
if (usernameColorDirection.text.isNotEmpty)
|
if (usernameColorDirection.text.isNotEmpty)
|
||||||
'direction': usernameColorDirection.text,
|
'direction': usernameColorDirection.text,
|
||||||
'colors':
|
'colors': usernameColorColors.value
|
||||||
usernameColorColors.value.where((c) => c.isNotEmpty).toList(),
|
.where((c) => c.isNotEmpty)
|
||||||
|
.toList(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,18 +203,16 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
'time_zone': timeZoneController.text,
|
'time_zone': timeZoneController.text,
|
||||||
'birthday': birthday.value?.toUtc().toIso8601String(),
|
'birthday': birthday.value?.toUtc().toIso8601String(),
|
||||||
'username_color': usernameColorData,
|
'username_color': usernameColorData,
|
||||||
'links':
|
'links': links.value
|
||||||
links.value
|
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
|
||||||
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
|
.toList(),
|
||||||
.toList(),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final userNotifier = ref.read(userInfoProvider.notifier);
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
userNotifier.fetchUser();
|
userNotifier.fetchUser();
|
||||||
links.value =
|
links.value = links.value
|
||||||
links.value
|
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
|
||||||
.where((e) => e.name.isNotEmpty && e.url.isNotEmpty)
|
.toList();
|
||||||
.toList();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -244,13 +239,12 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child:
|
child: user.value!.profile.background != null
|
||||||
user.value!.profile.background?.id != null
|
? CloudImageWidget(
|
||||||
? CloudImageWidget(
|
file: user.value!.profile.background,
|
||||||
fileId: user.value!.profile.background!.id,
|
fit: BoxFit.cover,
|
||||||
fit: BoxFit.cover,
|
)
|
||||||
)
|
: const SizedBox.shrink(),
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
updateProfilePicture('background');
|
updateProfilePicture('background');
|
||||||
@@ -261,7 +255,7 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
bottom: -32,
|
bottom: -32,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: user.value!.profile.picture?.id,
|
file: user.value!.profile.picture,
|
||||||
radius: 40,
|
radius: 40,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -291,14 +285,14 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
controller: usernameController,
|
controller: usernameController,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
decoration: InputDecoration(labelText: 'nickname'.tr()),
|
decoration: InputDecoration(labelText: 'nickname'.tr()),
|
||||||
controller: nicknameController,
|
controller: nicknameController,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
DropdownButtonFormField2<String>(
|
DropdownButtonFormField2<String>(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -385,9 +379,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
labelText: 'firstName'.tr(),
|
labelText: 'firstName'.tr(),
|
||||||
),
|
),
|
||||||
controller: firstNameController,
|
controller: firstNameController,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) =>
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -396,9 +389,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
labelText: 'middleName'.tr(),
|
labelText: 'middleName'.tr(),
|
||||||
),
|
),
|
||||||
controller: middleNameController,
|
controller: middleNameController,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) =>
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -407,9 +399,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
labelText: 'lastName'.tr(),
|
labelText: 'lastName'.tr(),
|
||||||
),
|
),
|
||||||
controller: lastNameController,
|
controller: lastNameController,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) =>
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -423,8 +414,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
maxLines: null,
|
maxLines: null,
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
controller: bioController,
|
controller: bioController,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
spacing: 16,
|
spacing: 16,
|
||||||
@@ -445,33 +436,34 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
onSelected: (String selection) {
|
onSelected: (String selection) {
|
||||||
genderController.text = selection;
|
genderController.text = selection;
|
||||||
},
|
},
|
||||||
fieldViewBuilder: (
|
fieldViewBuilder:
|
||||||
context,
|
(
|
||||||
controller,
|
context,
|
||||||
focusNode,
|
controller,
|
||||||
onFieldSubmitted,
|
focusNode,
|
||||||
) {
|
onFieldSubmitted,
|
||||||
// Initialize the controller with the current value
|
) {
|
||||||
if (controller.text.isEmpty &&
|
// Initialize the controller with the current value
|
||||||
genderController.text.isNotEmpty) {
|
if (controller.text.isEmpty &&
|
||||||
controller.text = genderController.text;
|
genderController.text.isNotEmpty) {
|
||||||
}
|
controller.text = genderController.text;
|
||||||
|
}
|
||||||
|
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'gender'.tr(),
|
labelText: 'gender'.tr(),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
genderController.text = value;
|
genderController.text = value;
|
||||||
|
},
|
||||||
|
onTapOutside: (_) => FocusManager
|
||||||
|
.instance
|
||||||
|
.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus
|
|
||||||
?.unfocus(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -480,9 +472,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
labelText: 'pronouns'.tr(),
|
labelText: 'pronouns'.tr(),
|
||||||
),
|
),
|
||||||
controller: pronounsController,
|
controller: pronounsController,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) =>
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -496,9 +487,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
labelText: 'location'.tr(),
|
labelText: 'location'.tr(),
|
||||||
),
|
),
|
||||||
controller: locationController,
|
controller: locationController,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) =>
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -507,8 +497,8 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
if (textEditingValue.text.isEmpty) {
|
if (textEditingValue.text.isEmpty) {
|
||||||
return const Iterable<String>.empty();
|
return const Iterable<String>.empty();
|
||||||
}
|
}
|
||||||
final lowercaseQuery =
|
final lowercaseQuery = textEditingValue.text
|
||||||
textEditingValue.text.toLowerCase();
|
.toLowerCase();
|
||||||
return getAvailableTz().where((tz) {
|
return getAvailableTz().where((tz) {
|
||||||
return tz.toLowerCase().contains(lowercaseQuery);
|
return tz.toLowerCase().contains(lowercaseQuery);
|
||||||
});
|
});
|
||||||
@@ -516,46 +506,49 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
onSelected: (String selection) {
|
onSelected: (String selection) {
|
||||||
timeZoneController.text = selection;
|
timeZoneController.text = selection;
|
||||||
},
|
},
|
||||||
fieldViewBuilder: (
|
fieldViewBuilder:
|
||||||
context,
|
(
|
||||||
controller,
|
context,
|
||||||
focusNode,
|
controller,
|
||||||
onFieldSubmitted,
|
focusNode,
|
||||||
) {
|
onFieldSubmitted,
|
||||||
// Sync the controller with timeZoneController when the widget is built
|
) {
|
||||||
if (controller.text != timeZoneController.text) {
|
// Sync the controller with timeZoneController when the widget is built
|
||||||
controller.text = timeZoneController.text;
|
if (controller.text !=
|
||||||
}
|
timeZoneController.text) {
|
||||||
|
controller.text = timeZoneController.text;
|
||||||
|
}
|
||||||
|
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'timeZone'.tr(),
|
labelText: 'timeZone'.tr(),
|
||||||
suffix: InkWell(
|
suffix: InkWell(
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Symbols.my_location,
|
Symbols.my_location,
|
||||||
size: 18,
|
size: 18,
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final machineTz =
|
||||||
|
await getMachineTz();
|
||||||
|
controller.text = machineTz;
|
||||||
|
timeZoneController.text = machineTz;
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) {
|
||||||
|
hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onChanged: (value) {
|
||||||
try {
|
timeZoneController.text = value;
|
||||||
showLoadingModal(context);
|
|
||||||
final machineTz = await getMachineTz();
|
|
||||||
controller.text = machineTz;
|
|
||||||
timeZoneController.text = machineTz;
|
|
||||||
} finally {
|
|
||||||
if (context.mounted) {
|
|
||||||
hideLoadingModal(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
),
|
|
||||||
onChanged: (value) {
|
|
||||||
timeZoneController.text = value;
|
|
||||||
},
|
},
|
||||||
);
|
|
||||||
},
|
|
||||||
optionsViewBuilder: (context, onSelected, options) {
|
optionsViewBuilder: (context, onSelected, options) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
@@ -569,21 +562,21 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
itemCount: options.length,
|
itemCount: options.length,
|
||||||
itemBuilder: (
|
itemBuilder:
|
||||||
BuildContext context,
|
(BuildContext context, int index) {
|
||||||
int index,
|
final option = options.elementAt(
|
||||||
) {
|
index,
|
||||||
final option = options.elementAt(index);
|
);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
option,
|
option,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onSelected(option);
|
onSelected(option);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -644,10 +637,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(
|
context,
|
||||||
context,
|
).colorScheme.surfaceContainerHighest,
|
||||||
).colorScheme.surfaceContainerHighest,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -664,25 +656,23 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
type: usernameColorType.value,
|
type: usernameColorType.value,
|
||||||
value:
|
value:
|
||||||
usernameColorType.value == 'plain' &&
|
usernameColorType.value == 'plain' &&
|
||||||
usernameColorValue
|
usernameColorValue.text.isNotEmpty
|
||||||
.text
|
? usernameColorValue.text
|
||||||
.isNotEmpty
|
: null,
|
||||||
? usernameColorValue.text
|
|
||||||
: null,
|
|
||||||
direction:
|
direction:
|
||||||
usernameColorType.value ==
|
usernameColorType.value ==
|
||||||
'gradient' &&
|
'gradient' &&
|
||||||
usernameColorDirection
|
usernameColorDirection
|
||||||
.text
|
.text
|
||||||
.isNotEmpty
|
.isNotEmpty
|
||||||
? usernameColorDirection.text
|
? usernameColorDirection.text
|
||||||
: null,
|
: null,
|
||||||
colors:
|
colors:
|
||||||
usernameColorType.value == 'gradient'
|
usernameColorType.value == 'gradient'
|
||||||
? usernameColorColors.value
|
? usernameColorColors.value
|
||||||
.where((c) => c.isNotEmpty)
|
.where((c) => c.isNotEmpty)
|
||||||
.toList()
|
.toList()
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -724,10 +714,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
? Symbols.check_circle
|
? Symbols.check_circle
|
||||||
: Symbols.error,
|
: Symbols.error,
|
||||||
size: 16,
|
size: 16,
|
||||||
color:
|
color: canUseColor
|
||||||
canUseColor
|
? Colors.green
|
||||||
? Colors.green
|
: Colors.red,
|
||||||
: Colors.red,
|
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
@@ -736,10 +725,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
: 'upgradeRequired'.tr(),
|
: 'upgradeRequired'.tr(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color:
|
color: canUseColor
|
||||||
canUseColor
|
? Colors.green
|
||||||
? Colors.green
|
: Colors.red,
|
||||||
: Colors.red,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -792,34 +780,35 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
onSelected: (String selection) {
|
onSelected: (String selection) {
|
||||||
usernameColorValue.text = selection;
|
usernameColorValue.text = selection;
|
||||||
},
|
},
|
||||||
fieldViewBuilder: (
|
fieldViewBuilder:
|
||||||
context,
|
(
|
||||||
controller,
|
context,
|
||||||
focusNode,
|
controller,
|
||||||
onFieldSubmitted,
|
focusNode,
|
||||||
) {
|
onFieldSubmitted,
|
||||||
// Initialize the controller with the current value
|
) {
|
||||||
if (controller.text.isEmpty &&
|
// Initialize the controller with the current value
|
||||||
usernameColorValue.text.isNotEmpty) {
|
if (controller.text.isEmpty &&
|
||||||
controller.text = usernameColorValue.text;
|
usernameColorValue.text.isNotEmpty) {
|
||||||
}
|
controller.text = usernameColorValue.text;
|
||||||
|
}
|
||||||
|
|
||||||
return TextFormField(
|
return TextFormField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'colorValue'.tr(),
|
labelText: 'colorValue'.tr(),
|
||||||
hintText: 'e.g. red or #ff6600',
|
hintText: 'e.g. red or #ff6600',
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
usernameColorValue.text = value;
|
usernameColorValue.text = value;
|
||||||
|
},
|
||||||
|
onTapOutside: (_) => FocusManager
|
||||||
|
.instance
|
||||||
|
.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus
|
|
||||||
?.unfocus(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
if (usernameColorType.value == 'gradient') ...[
|
if (usernameColorType.value == 'gradient') ...[
|
||||||
DropdownButtonFormField2<String>(
|
DropdownButtonFormField2<String>(
|
||||||
@@ -862,10 +851,9 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
child: Text('gradientDirectionToTopLeft'.tr()),
|
child: Text('gradientDirectionToTopLeft'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
value:
|
value: usernameColorDirection.text.isNotEmpty
|
||||||
usernameColorDirection.text.isNotEmpty
|
? usernameColorDirection.text
|
||||||
? usernameColorDirection.text
|
: 'to right',
|
||||||
: 'to right',
|
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
usernameColorDirection.text = value ?? 'to right';
|
usernameColorDirection.text = value ?? 'to right';
|
||||||
},
|
},
|
||||||
@@ -911,21 +899,19 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
usernameColorColors.value[i] = value;
|
usernameColorColors.value[i] = value;
|
||||||
},
|
},
|
||||||
onTapOutside:
|
onTapOutside: (_) => FocusManager
|
||||||
(_) =>
|
.instance
|
||||||
FocusManager.instance.primaryFocus
|
.primaryFocus
|
||||||
?.unfocus(),
|
?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.delete),
|
icon: const Icon(Symbols.delete),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
usernameColorColors.value =
|
usernameColorColors
|
||||||
usernameColorColors.value
|
.value = usernameColorColors.value
|
||||||
.whereIndexed(
|
.whereIndexed((idx, _) => idx != i)
|
||||||
(idx, _) => idx != i,
|
.toList();
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -968,10 +954,10 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
name: value,
|
name: value,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onTapOutside:
|
onTapOutside: (_) => FocusManager
|
||||||
(_) =>
|
.instance
|
||||||
FocusManager.instance.primaryFocus
|
.primaryFocus
|
||||||
?.unfocus(),
|
?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@@ -987,19 +973,18 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
url: value,
|
url: value,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onTapOutside:
|
onTapOutside: (_) => FocusManager
|
||||||
(_) =>
|
.instance
|
||||||
FocusManager.instance.primaryFocus
|
.primaryFocus
|
||||||
?.unfocus(),
|
?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.delete),
|
icon: const Icon(Symbols.delete),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
links.value =
|
links.value = links.value
|
||||||
links.value
|
.whereIndexed((idx, _) => idx != i)
|
||||||
.whereIndexed((idx, _) => idx != i)
|
.toList();
|
||||||
.toList();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class _AccountBasicInfo extends StatelessWidget {
|
|||||||
return Card(
|
return Card(
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final hasBackground = data.profile.background?.id != null;
|
final hasBackground = data.profile.background != null;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -962,7 +962,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
flexibleSpace: Stack(
|
flexibleSpace: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: data.profile.background?.id != null
|
child: data.profile.background != null
|
||||||
? CloudImageWidget(
|
? CloudImageWidget(
|
||||||
file: data.profile.background,
|
file: data.profile.background,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class RelationshipListTile extends StatelessWidget {
|
|||||||
contentPadding: const EdgeInsets.only(left: 16, right: 12),
|
contentPadding: const EdgeInsets.only(left: 16, right: 12),
|
||||||
leading: AccountPfcGestureDetector(
|
leading: AccountPfcGestureDetector(
|
||||||
uname: account.name,
|
uname: account.name,
|
||||||
child: ProfilePictureWidget(fileId: account.profile.picture?.id),
|
child: ProfilePictureWidget(file: account.profile.picture),
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
spacing: 6,
|
spacing: 6,
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
bottom: -32,
|
bottom: -32,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: picture.value?.id,
|
file: picture.value,
|
||||||
radius: 40,
|
radius: 40,
|
||||||
fallbackIcon: Symbols.group,
|
fallbackIcon: Symbols.group,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -98,15 +98,15 @@ class PublicRoomPreview extends HookConsumerWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 26,
|
height: 26,
|
||||||
width: 26,
|
width: 26,
|
||||||
child: (room.type == 1 && room.picture?.id == null)
|
child: (room.type == 1 && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: room.members!
|
files: room.members!
|
||||||
.map((e) => e.account.profile.picture?.id)
|
.map((e) => e.account.profile.picture)
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
)
|
||||||
: room.picture?.id != null
|
: room.picture != null
|
||||||
? ProfilePictureWidget(
|
? ProfilePictureWidget(
|
||||||
fileId: room.picture?.id,
|
file: room.picture,
|
||||||
fallbackIcon: Symbols.chat,
|
fallbackIcon: Symbols.chat,
|
||||||
)
|
)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
@@ -131,15 +131,15 @@ class PublicRoomPreview extends HookConsumerWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 26,
|
height: 26,
|
||||||
width: 26,
|
width: 26,
|
||||||
child: (room.type == 1 && room.picture?.id == null)
|
child: (room.type == 1 && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: room.members!
|
files: room.members!
|
||||||
.map((e) => e.account.profile.picture?.id)
|
.map((e) => e.account.profile.picture)
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
)
|
||||||
: room.picture?.id != null
|
: room.picture != null
|
||||||
? ProfilePictureWidget(
|
? ProfilePictureWidget(
|
||||||
fileId: room.picture?.id,
|
file: room.picture,
|
||||||
fallbackIcon: Symbols.chat,
|
fallbackIcon: Symbols.chat,
|
||||||
)
|
)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
|
|||||||
@@ -427,15 +427,15 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 26,
|
height: 26,
|
||||||
width: 26,
|
width: 26,
|
||||||
child: (room!.type == 1 && room.picture?.id == null)
|
child: (room!.type == 1 && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: getValidMembers(
|
files: getValidMembers(
|
||||||
room.members!,
|
room.members!,
|
||||||
).map((e) => e.account.profile.picture?.id).toList(),
|
).map((e) => e.account.profile.picture).toList(),
|
||||||
)
|
)
|
||||||
: room.picture?.id != null
|
: room.picture != null
|
||||||
? ProfilePictureWidget(
|
? ProfilePictureWidget(
|
||||||
fileId: room.picture?.id,
|
file: room.picture,
|
||||||
fallbackIcon: Symbols.chat,
|
fallbackIcon: Symbols.chat,
|
||||||
)
|
)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
@@ -473,15 +473,15 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 28,
|
height: 28,
|
||||||
width: 28,
|
width: 28,
|
||||||
child: (room!.type == 1 && room.picture?.id == null)
|
child: (room!.type == 1 && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: getValidMembers(
|
files: getValidMembers(
|
||||||
room.members!,
|
room.members!,
|
||||||
).map((e) => e.account.profile.picture?.id).toList(),
|
).map((e) => e.account.profile.picture).toList(),
|
||||||
)
|
)
|
||||||
: room.picture?.id != null
|
: room.picture != null
|
||||||
? ProfilePictureWidget(
|
? ProfilePictureWidget(
|
||||||
fileId: room.picture?.id,
|
file: room.picture,
|
||||||
fallbackIcon: Symbols.chat,
|
fallbackIcon: Symbols.chat,
|
||||||
)
|
)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
|
|||||||
@@ -279,9 +279,8 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
leading: PageBackButton(shadows: [iconShadow]),
|
leading: PageBackButton(shadows: [iconShadow]),
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background:
|
background:
|
||||||
(currentRoom!.type == 1 &&
|
(currentRoom!.type == 1 && currentRoom.background != null)
|
||||||
currentRoom.background?.id != null)
|
? CloudImageWidget(file: currentRoom.background!)
|
||||||
? CloudImageWidget(fileId: currentRoom.background!.id)
|
|
||||||
: (currentRoom.type == 1 &&
|
: (currentRoom.type == 1 &&
|
||||||
currentRoom.members!.length == 1 &&
|
currentRoom.members!.length == 1 &&
|
||||||
currentRoom
|
currentRoom
|
||||||
@@ -293,17 +292,16 @@ class ChatDetailScreen extends HookConsumerWidget {
|
|||||||
?.id !=
|
?.id !=
|
||||||
null)
|
null)
|
||||||
? CloudImageWidget(
|
? CloudImageWidget(
|
||||||
fileId: currentRoom
|
file: currentRoom
|
||||||
.members!
|
.members!
|
||||||
.first
|
.first
|
||||||
.account
|
.account
|
||||||
.profile
|
.profile
|
||||||
.background!
|
.background!,
|
||||||
.id,
|
|
||||||
)
|
)
|
||||||
: currentRoom.background?.id != null
|
: currentRoom.background != null
|
||||||
? CloudImageWidget(
|
? CloudImageWidget(
|
||||||
fileId: currentRoom.background!.id,
|
file: currentRoom.background!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
: Container(
|
: Container(
|
||||||
@@ -702,7 +700,7 @@ class _ChatMemberListSheet extends HookConsumerWidget {
|
|||||||
leading: AccountPfcGestureDetector(
|
leading: AccountPfcGestureDetector(
|
||||||
uname: member.account.name,
|
uname: member.account.name,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: member.account.profile.picture?.id,
|
file: member.account.profile.picture,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class PublisherSelector extends StatelessWidget {
|
|||||||
if (isReadOnly || currentPublisher == null) {
|
if (isReadOnly || currentPublisher == null) {
|
||||||
return ProfilePictureWidget(
|
return ProfilePictureWidget(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
fileId: currentPublisher?.picture?.id,
|
file: currentPublisher?.picture,
|
||||||
).center().padding(right: 8);
|
).center().padding(right: 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ class PublisherSelector extends StatelessWidget {
|
|||||||
.map(
|
.map(
|
||||||
(e) => ProfilePictureWidget(
|
(e) => ProfilePictureWidget(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
fileId: e.value?.picture?.id,
|
file: e.value?.picture,
|
||||||
).center().padding(right: 8),
|
).center().padding(right: 8),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -355,10 +355,7 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
value: item,
|
value: item,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(radius: 16, file: item.picture),
|
||||||
radius: 16,
|
|
||||||
fileId: item.picture?.id,
|
|
||||||
),
|
|
||||||
title: Text(item.nick),
|
title: Text(item.nick),
|
||||||
subtitle: Text('@${item.name}'),
|
subtitle: Text('@${item.name}'),
|
||||||
trailing: currentPublisher.value?.id == item.id
|
trailing: currentPublisher.value?.id == item.id
|
||||||
@@ -889,7 +886,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: member.account!.profile.picture?.id,
|
file: member.account!.profile.picture,
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
spacing: 6,
|
spacing: 6,
|
||||||
@@ -1137,7 +1134,7 @@ class _PublisherInviteSheet extends HookConsumerWidget {
|
|||||||
final invite = items[index];
|
final invite = items[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: invite.publisher!.picture?.id,
|
file: invite.publisher!.picture,
|
||||||
fallbackIcon: Symbols.group,
|
fallbackIcon: Symbols.group,
|
||||||
),
|
),
|
||||||
title: Text(invite.publisher!.nick),
|
title: Text(invite.publisher!.nick),
|
||||||
|
|||||||
@@ -69,157 +69,141 @@ class StickerPackDetailContent extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pack.when(
|
return pack.when(
|
||||||
data:
|
data: (pack) => Column(
|
||||||
(pack) => Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Text(pack!.description),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
Row(
|
||||||
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
Text(pack!.description),
|
const Icon(Symbols.folder, size: 16),
|
||||||
Row(
|
Text(
|
||||||
spacing: 4,
|
'${packContent.value?.length ?? 0}/24',
|
||||||
children: [
|
style: GoogleFonts.robotoMono(),
|
||||||
const Icon(Symbols.folder, size: 16),
|
),
|
||||||
Text(
|
|
||||||
'${packContent.value?.length ?? 0}/24',
|
|
||||||
style: GoogleFonts.robotoMono(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).opacity(0.85),
|
|
||||||
Row(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.sell, size: 16),
|
|
||||||
Text(pack.prefix, style: GoogleFonts.robotoMono()),
|
|
||||||
],
|
|
||||||
).opacity(0.85),
|
|
||||||
Row(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.tag, size: 16),
|
|
||||||
Flexible(
|
|
||||||
child: SelectableText(
|
|
||||||
pack.id,
|
|
||||||
maxLines: 1,
|
|
||||||
style: GoogleFonts.robotoMono(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).opacity(0.85),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24, vertical: 24),
|
).opacity(0.85),
|
||||||
const Divider(height: 1),
|
Row(
|
||||||
Expanded(
|
spacing: 4,
|
||||||
child: packContent.when(
|
children: [
|
||||||
data:
|
const Icon(Symbols.sell, size: 16),
|
||||||
(stickers) => RefreshIndicator(
|
Text(pack.prefix, style: GoogleFonts.robotoMono()),
|
||||||
onRefresh:
|
],
|
||||||
() => ref.refresh(
|
).opacity(0.85),
|
||||||
stickerPackContentProvider(id).future,
|
Row(
|
||||||
),
|
spacing: 4,
|
||||||
child: GridView.builder(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(
|
const Icon(Symbols.tag, size: 16),
|
||||||
horizontal: 24,
|
Flexible(
|
||||||
vertical: 20,
|
child: SelectableText(
|
||||||
),
|
pack.id,
|
||||||
gridDelegate:
|
maxLines: 1,
|
||||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
style: GoogleFonts.robotoMono(),
|
||||||
maxCrossAxisExtent: 80,
|
),
|
||||||
mainAxisSpacing: 8,
|
),
|
||||||
crossAxisSpacing: 8,
|
],
|
||||||
),
|
).opacity(0.85),
|
||||||
itemCount: stickers.length,
|
],
|
||||||
itemBuilder: (context, index) {
|
).padding(horizontal: 24, vertical: 24),
|
||||||
final sticker = stickers[index];
|
const Divider(height: 1),
|
||||||
return ContextMenuWidget(
|
Expanded(
|
||||||
menuProvider: (_) {
|
child: packContent.when(
|
||||||
return Menu(
|
data: (stickers) => RefreshIndicator(
|
||||||
children: [
|
onRefresh: () =>
|
||||||
MenuAction(
|
ref.refresh(stickerPackContentProvider(id).future),
|
||||||
title: 'stickerCopyPlaceholder'.tr(),
|
child: GridView.builder(
|
||||||
image: MenuImage.icon(Symbols.copy_all),
|
padding: const EdgeInsets.symmetric(
|
||||||
callback: () {
|
horizontal: 24,
|
||||||
Clipboard.setData(
|
vertical: 20,
|
||||||
ClipboardData(
|
),
|
||||||
text:
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
':${pack.prefix}+${sticker.slug}:',
|
maxCrossAxisExtent: 80,
|
||||||
),
|
mainAxisSpacing: 8,
|
||||||
);
|
crossAxisSpacing: 8,
|
||||||
},
|
),
|
||||||
),
|
itemCount: stickers.length,
|
||||||
MenuSeparator(),
|
itemBuilder: (context, index) {
|
||||||
MenuAction(
|
final sticker = stickers[index];
|
||||||
title: 'edit'.tr(),
|
return ContextMenuWidget(
|
||||||
image: MenuImage.icon(Symbols.edit),
|
menuProvider: (_) {
|
||||||
callback: () {
|
return Menu(
|
||||||
showModalBottomSheet(
|
children: [
|
||||||
context: context,
|
MenuAction(
|
||||||
isScrollControlled: true,
|
title: 'stickerCopyPlaceholder'.tr(),
|
||||||
builder:
|
image: MenuImage.icon(Symbols.copy_all),
|
||||||
(context) => SheetScaffold(
|
callback: () {
|
||||||
titleText: 'editSticker'.tr(),
|
Clipboard.setData(
|
||||||
child: StickerForm(
|
ClipboardData(
|
||||||
packId: id,
|
text: ':${pack.prefix}+${sticker.slug}:',
|
||||||
id: sticker.id,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
).then((value) {
|
|
||||||
if (value != null) {
|
|
||||||
ref.invalidate(
|
|
||||||
stickerPackContentProvider(id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuAction(
|
|
||||||
title: 'delete'.tr(),
|
|
||||||
image: MenuImage.icon(Symbols.delete),
|
|
||||||
callback: () {
|
|
||||||
deleteSticker(sticker);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ClipRRect(
|
),
|
||||||
borderRadius: BorderRadius.all(
|
MenuSeparator(),
|
||||||
Radius.circular(8),
|
MenuAction(
|
||||||
),
|
title: 'edit'.tr(),
|
||||||
child: Container(
|
image: MenuImage.icon(Symbols.edit),
|
||||||
decoration: BoxDecoration(
|
callback: () {
|
||||||
color:
|
showModalBottomSheet(
|
||||||
Theme.of(
|
context: context,
|
||||||
context,
|
isScrollControlled: true,
|
||||||
).colorScheme.surfaceContainer,
|
builder: (context) => SheetScaffold(
|
||||||
borderRadius: BorderRadius.all(
|
titleText: 'editSticker'.tr(),
|
||||||
Radius.circular(8),
|
child: StickerForm(
|
||||||
|
packId: id,
|
||||||
|
id: sticker.id,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: CloudImageWidget(
|
).then((value) {
|
||||||
fileId: sticker.image.id,
|
if (value != null) {
|
||||||
fit: BoxFit.contain,
|
ref.invalidate(
|
||||||
),
|
stickerPackContentProvider(id),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
);
|
});
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
MenuAction(
|
||||||
|
title: 'delete'.tr(),
|
||||||
|
image: MenuImage.icon(Symbols.delete),
|
||||||
|
callback: () {
|
||||||
|
deleteSticker(sticker);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainer,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
child: CloudImageWidget(
|
||||||
|
file: sticker.image,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
error:
|
);
|
||||||
(err, _) =>
|
},
|
||||||
Text(
|
|
||||||
'Error: $err',
|
|
||||||
).textAlignment(TextAlign.center).center(),
|
|
||||||
loading: () => const CircularProgressIndicator().center(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
error: (err, _) =>
|
||||||
|
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
||||||
|
loading: () => const CircularProgressIndicator().center(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
error:
|
],
|
||||||
(err, _) =>
|
),
|
||||||
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
error: (err, _) =>
|
||||||
|
Text('Error: $err').textAlignment(TextAlign.center).center(),
|
||||||
loading: () => const CircularProgressIndicator().center(),
|
loading: () => const CircularProgressIndicator().center(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -241,65 +225,60 @@ class StickerPackActionMenu extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return PopupMenuButton(
|
return PopupMenuButton(
|
||||||
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
|
icon: Icon(Icons.more_vert, shadows: [iconShadow]),
|
||||||
itemBuilder:
|
itemBuilder: (context) => [
|
||||||
(context) => [
|
PopupMenuItem(
|
||||||
PopupMenuItem(
|
onTap: () {
|
||||||
onTap: () {
|
showModalBottomSheet(
|
||||||
showModalBottomSheet(
|
context: context,
|
||||||
context: context,
|
isScrollControlled: true,
|
||||||
isScrollControlled: true,
|
builder: (context) => SheetScaffold(
|
||||||
builder:
|
titleText: 'editStickerPack'.tr(),
|
||||||
(context) => SheetScaffold(
|
child: StickerPackForm(pubName: pubName, packId: packId),
|
||||||
titleText: 'editStickerPack'.tr(),
|
|
||||||
child: StickerPackForm(
|
|
||||||
pubName: pubName,
|
|
||||||
packId: packId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).then((value) {
|
|
||||||
if (value != null) {
|
|
||||||
ref.invalidate(stickerPackProvider(packId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.edit,
|
|
||||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
const Text('editStickerPack').tr(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
).then((value) {
|
||||||
PopupMenuItem(
|
if (value != null) {
|
||||||
child: Row(
|
ref.invalidate(stickerPackProvider(packId));
|
||||||
children: [
|
}
|
||||||
const Icon(Icons.delete, color: Colors.red),
|
});
|
||||||
const Gap(12),
|
},
|
||||||
const Text(
|
child: Row(
|
||||||
'deleteStickerPack',
|
children: [
|
||||||
style: TextStyle(color: Colors.red),
|
Icon(
|
||||||
).tr(),
|
Icons.edit,
|
||||||
],
|
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
),
|
),
|
||||||
onTap: () {
|
const Gap(12),
|
||||||
showConfirmAlert(
|
const Text('editStickerPack').tr(),
|
||||||
'deleteStickerPackHint'.tr(),
|
],
|
||||||
'deleteStickerPack'.tr(),
|
),
|
||||||
isDanger: true,
|
),
|
||||||
).then((confirm) {
|
PopupMenuItem(
|
||||||
if (confirm) {
|
child: Row(
|
||||||
final client = ref.watch(apiClientProvider);
|
children: [
|
||||||
client.delete('/sphere/stickers/$packId');
|
const Icon(Icons.delete, color: Colors.red),
|
||||||
ref.invalidate(stickerPacksProvider);
|
const Gap(12),
|
||||||
if (context.mounted) context.pop(true);
|
const Text(
|
||||||
}
|
'deleteStickerPack',
|
||||||
});
|
style: TextStyle(color: Colors.red),
|
||||||
},
|
).tr(),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
|
onTap: () {
|
||||||
|
showConfirmAlert(
|
||||||
|
'deleteStickerPackHint'.tr(),
|
||||||
|
'deleteStickerPack'.tr(),
|
||||||
|
isDanger: true,
|
||||||
|
).then((confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
client.delete('/sphere/stickers/$packId');
|
||||||
|
ref.invalidate(stickerPacksProvider);
|
||||||
|
if (context.mounted) context.pop(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,10 +351,9 @@ class StickerForm extends HookConsumerWidget {
|
|||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
),
|
),
|
||||||
child:
|
child: (image.value?.isEmpty ?? true)
|
||||||
(image.value?.isEmpty ?? true)
|
? const SizedBox.shrink()
|
||||||
? const SizedBox.shrink()
|
: CloudImageWidget(fileId: image.value!),
|
||||||
: CloudImageWidget(fileId: image.value!),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -383,10 +361,8 @@ class StickerForm extends HookConsumerWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder: (context) =>
|
||||||
(context) => CloudFilePicker(
|
CloudFilePicker(allowedTypes: {UniversalFileType.image}),
|
||||||
allowedTypes: {UniversalFileType.image},
|
|
||||||
),
|
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
image.value = value[0].id;
|
image.value = value[0].id;
|
||||||
@@ -412,8 +388,8 @@ class StickerForm extends HookConsumerWidget {
|
|||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class _AppOverview extends StatelessWidget {
|
|||||||
left: 20,
|
left: 20,
|
||||||
bottom: -32,
|
bottom: -32,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: app.picture?.id,
|
file: app.picture,
|
||||||
radius: 40,
|
radius: 40,
|
||||||
fallbackIcon: Symbols.apps,
|
fallbackIcon: Symbols.apps,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(app.name),
|
title: Text(app.name),
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: app.picture?.id,
|
file: app.picture,
|
||||||
fallbackIcon: Symbols.apps,
|
fallbackIcon: Symbols.apps,
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ class _BotOverview extends StatelessWidget {
|
|||||||
left: 20,
|
left: 20,
|
||||||
bottom: -32,
|
bottom: -32,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: bot.account.profile.picture?.id,
|
file: bot.account.profile.picture,
|
||||||
radius: 40,
|
radius: 40,
|
||||||
fallbackIcon: Symbols.smart_toy,
|
fallbackIcon: Symbols.smart_toy,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -51,10 +51,9 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isNew = id == null;
|
final isNew = id == null;
|
||||||
final app =
|
final app = isNew
|
||||||
isNew
|
? null
|
||||||
? null
|
: ref.watch(customAppProvider(publisherName, projectId, id!));
|
||||||
: ref.watch(customAppProvider(publisherName, projectId, id!));
|
|
||||||
|
|
||||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||||
|
|
||||||
@@ -139,14 +138,10 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final cloudFile =
|
final cloudFile = await FileUploader.createCloudFile(
|
||||||
await FileUploader.createCloudFile(
|
ref: ref,
|
||||||
ref: ref,
|
fileData: UniversalFile(data: result, type: UniversalFileType.image),
|
||||||
fileData: UniversalFile(
|
).future;
|
||||||
data: result,
|
|
||||||
type: UniversalFileType.image,
|
|
||||||
),
|
|
||||||
).future;
|
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
}
|
}
|
||||||
@@ -169,41 +164,40 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) => SheetScaffold(
|
||||||
(context) => SheetScaffold(
|
titleText: 'addScope'.tr(),
|
||||||
titleText: 'addScope'.tr(),
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(20),
|
||||||
padding: const EdgeInsets.all(20),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
TextFormField(
|
||||||
TextFormField(
|
controller: scopeController,
|
||||||
controller: scopeController,
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
labelText: 'scopeName'.tr(),
|
||||||
labelText: 'scopeName'.tr(),
|
border: OutlineInputBorder(
|
||||||
border: OutlineInputBorder(
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
FilledButton.tonalIcon(
|
|
||||||
onPressed: () {
|
|
||||||
if (scopeController.text.isNotEmpty) {
|
|
||||||
allowedScopes.value = [
|
|
||||||
...allowedScopes.value,
|
|
||||||
scopeController.text,
|
|
||||||
];
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: const Icon(Symbols.add),
|
|
||||||
label: Text('add').tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: () {
|
||||||
|
if (scopeController.text.isNotEmpty) {
|
||||||
|
allowedScopes.value = [
|
||||||
|
...allowedScopes.value,
|
||||||
|
scopeController.text,
|
||||||
|
];
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
label: Text('add').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,57 +206,56 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) => SheetScaffold(
|
||||||
(context) => SheetScaffold(
|
titleText: 'addRedirectUri'.tr(),
|
||||||
titleText: 'addRedirectUri'.tr(),
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(20),
|
||||||
padding: const EdgeInsets.all(20),
|
child: Column(
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
children: [
|
||||||
children: [
|
TextFormField(
|
||||||
TextFormField(
|
controller: uriController,
|
||||||
controller: uriController,
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
labelText: 'redirectUri'.tr(),
|
||||||
labelText: 'redirectUri'.tr(),
|
hintText: 'https://example.com/auth/callback',
|
||||||
hintText: 'https://example.com/auth/callback',
|
helperText: 'redirectUriHint'.tr(),
|
||||||
helperText: 'redirectUriHint'.tr(),
|
helperMaxLines: 3,
|
||||||
helperMaxLines: 3,
|
border: OutlineInputBorder(
|
||||||
border: OutlineInputBorder(
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return 'uriRequired'.tr();
|
|
||||||
}
|
|
||||||
final uri = Uri.tryParse(value);
|
|
||||||
if (uri == null || !uri.hasAbsolutePath) {
|
|
||||||
return 'invalidUri'.tr();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
FilledButton.tonalIcon(
|
keyboardType: TextInputType.url,
|
||||||
onPressed: () {
|
validator: (value) {
|
||||||
if (uriController.text.isNotEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
redirectUris.value = [
|
return 'uriRequired'.tr();
|
||||||
...redirectUris.value,
|
}
|
||||||
uriController.text,
|
final uri = Uri.tryParse(value);
|
||||||
];
|
if (uri == null || !uri.hasAbsolutePath) {
|
||||||
Navigator.pop(context);
|
return 'invalidUri'.tr();
|
||||||
}
|
}
|
||||||
},
|
return null;
|
||||||
icon: const Icon(Symbols.add),
|
},
|
||||||
label: Text('add').tr(),
|
onTapOutside: (_) =>
|
||||||
),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: () {
|
||||||
|
if (uriController.text.isNotEmpty) {
|
||||||
|
redirectUris.value = [
|
||||||
|
...redirectUris.value,
|
||||||
|
uriController.text,
|
||||||
|
];
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
label: Text('add').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,31 +268,28 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
'picture_id': picture.value?.id,
|
'picture_id': picture.value?.id,
|
||||||
'background_id': background.value?.id,
|
'background_id': background.value?.id,
|
||||||
'links': {
|
'links': {
|
||||||
'home_page':
|
'home_page': homePageController.text.isNotEmpty
|
||||||
homePageController.text.isNotEmpty
|
? homePageController.text
|
||||||
? homePageController.text
|
: null,
|
||||||
: null,
|
'privacy_policy': privacyPolicyController.text.isNotEmpty
|
||||||
'privacy_policy':
|
? privacyPolicyController.text
|
||||||
privacyPolicyController.text.isNotEmpty
|
: null,
|
||||||
? privacyPolicyController.text
|
'terms_of_service': termsController.text.isNotEmpty
|
||||||
: null,
|
? termsController.text
|
||||||
'terms_of_service':
|
: null,
|
||||||
termsController.text.isNotEmpty ? termsController.text : null,
|
|
||||||
},
|
},
|
||||||
'oauth_config':
|
'oauth_config': oauthEnabled.value
|
||||||
oauthEnabled.value
|
? {
|
||||||
? {
|
'redirect_uris': redirectUris.value,
|
||||||
'redirect_uris': redirectUris.value,
|
'post_logout_redirect_uris': postLogoutUris.value.isNotEmpty
|
||||||
'post_logout_redirect_uris':
|
? postLogoutUris.value
|
||||||
postLogoutUris.value.isNotEmpty
|
: null,
|
||||||
? postLogoutUris.value
|
'allowed_scopes': allowedScopes.value,
|
||||||
: null,
|
'allowed_grant_types': allowedGrantTypes.value,
|
||||||
'allowed_scopes': allowedScopes.value,
|
'require_pkce': requirePkce.value,
|
||||||
'allowed_grant_types': allowedGrantTypes.value,
|
'allow_offline_access': allowOfflineAccess.value,
|
||||||
'require_pkce': requirePkce.value,
|
}
|
||||||
'allow_offline_access': allowOfflineAccess.value,
|
: null,
|
||||||
}
|
|
||||||
: null,
|
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
@@ -326,287 +316,269 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final bodyContent =
|
final bodyContent = app == null && !isNew
|
||||||
app == null && !isNew
|
? const Center(child: CircularProgressIndicator())
|
||||||
? const Center(child: CircularProgressIndicator())
|
: app?.hasError == true && !isNew
|
||||||
: app?.hasError == true && !isNew
|
? ResponseErrorWidget(
|
||||||
? ResponseErrorWidget(
|
error: app!.error,
|
||||||
error: app!.error,
|
onRetry: () => ref.invalidate(
|
||||||
onRetry:
|
customAppProvider(publisherName, projectId, id!),
|
||||||
() => ref.invalidate(
|
),
|
||||||
customAppProvider(publisherName, projectId, id!),
|
)
|
||||||
),
|
: SingleChildScrollView(
|
||||||
)
|
child: Column(
|
||||||
: SingleChildScrollView(
|
children: [
|
||||||
child: Column(
|
AspectRatio(
|
||||||
children: [
|
aspectRatio: 16 / 7,
|
||||||
AspectRatio(
|
child: Stack(
|
||||||
aspectRatio: 16 / 7,
|
clipBehavior: Clip.none,
|
||||||
child: Stack(
|
fit: StackFit.expand,
|
||||||
clipBehavior: Clip.none,
|
children: [
|
||||||
fit: StackFit.expand,
|
GestureDetector(
|
||||||
children: [
|
child: Container(
|
||||||
GestureDetector(
|
color: Theme.of(
|
||||||
child: Container(
|
context,
|
||||||
color:
|
).colorScheme.surfaceContainerHigh,
|
||||||
Theme.of(
|
child: background.value != null
|
||||||
context,
|
? CloudFileWidget(
|
||||||
).colorScheme.surfaceContainerHigh,
|
item: background.value!,
|
||||||
child:
|
fit: BoxFit.cover,
|
||||||
background.value != null
|
)
|
||||||
? CloudFileWidget(
|
: const SizedBox.shrink(),
|
||||||
item: background.value!,
|
),
|
||||||
fit: BoxFit.cover,
|
onTap: () {
|
||||||
)
|
setPicture('background');
|
||||||
: const SizedBox.shrink(),
|
},
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 20,
|
||||||
|
bottom: -32,
|
||||||
|
child: GestureDetector(
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: picture.value,
|
||||||
|
radius: 40,
|
||||||
|
fallbackIcon: Symbols.apps,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setPicture('background');
|
setPicture('picture');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Positioned(
|
),
|
||||||
left: 20,
|
],
|
||||||
bottom: -32,
|
|
||||||
child: GestureDetector(
|
|
||||||
child: ProfilePictureWidget(
|
|
||||||
fileId: picture.value?.id,
|
|
||||||
radius: 40,
|
|
||||||
fallbackIcon: Symbols.apps,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
setPicture('picture');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).padding(bottom: 32),
|
|
||||||
Form(
|
|
||||||
key: formKey,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: nameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'name'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: slugController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'slug'.tr(),
|
|
||||||
helperText: 'slugHint'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: descriptionController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'description'.tr(),
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
ExpansionPanelList(
|
|
||||||
expansionCallback: (index, isExpanded) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
enableLinks.value = isExpanded;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
oauthEnabled.value = isExpanded;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
ExpansionPanel(
|
|
||||||
headerBuilder:
|
|
||||||
(context, isExpanded) =>
|
|
||||||
ListTile(title: Text('appLinks').tr()),
|
|
||||||
body: Column(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: homePageController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'homePageUrl'.tr(),
|
|
||||||
hintText: 'https://example.com',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
),
|
|
||||||
TextFormField(
|
|
||||||
controller: privacyPolicyController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'privacyPolicyUrl'.tr(),
|
|
||||||
hintText: 'https://example.com/privacy',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
),
|
|
||||||
TextFormField(
|
|
||||||
controller: termsController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'termsOfServiceUrl'.tr(),
|
|
||||||
hintText: 'https://example.com/terms',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 16, bottom: 24),
|
|
||||||
isExpanded: enableLinks.value,
|
|
||||||
),
|
|
||||||
ExpansionPanel(
|
|
||||||
headerBuilder:
|
|
||||||
(context, isExpanded) =>
|
|
||||||
ListTile(title: Text('oauthConfig').tr()),
|
|
||||||
body: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('redirectUris'.tr()),
|
|
||||||
Card(
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
...redirectUris.value.map(
|
|
||||||
(uri) => ListTile(
|
|
||||||
title: Text(uri),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Symbols.delete),
|
|
||||||
onPressed: () {
|
|
||||||
redirectUris.value =
|
|
||||||
redirectUris.value
|
|
||||||
.where((u) => u != uri)
|
|
||||||
.toList();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (redirectUris.value.isNotEmpty)
|
|
||||||
const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Symbols.add),
|
|
||||||
title: Text('addRedirectUri'.tr()),
|
|
||||||
onTap: showAddRedirectUriDialog,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text('allowedScopes'.tr()),
|
|
||||||
Card(
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
...allowedScopes.value.map(
|
|
||||||
(scope) => ListTile(
|
|
||||||
title: Text(scope),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Symbols.delete),
|
|
||||||
onPressed: () {
|
|
||||||
allowedScopes.value =
|
|
||||||
allowedScopes.value
|
|
||||||
.where(
|
|
||||||
(s) => s != scope,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (allowedScopes.value.isNotEmpty)
|
|
||||||
const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Symbols.add),
|
|
||||||
title: Text('add').tr(),
|
|
||||||
onTap: showAddScopeDialog,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
SwitchListTile(
|
|
||||||
title: Text('requirePkce'.tr()),
|
|
||||||
value: requirePkce.value,
|
|
||||||
onChanged:
|
|
||||||
(value) => requirePkce.value = value,
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
title: Text('allowOfflineAccess'.tr()),
|
|
||||||
value: allowOfflineAccess.value,
|
|
||||||
onChanged:
|
|
||||||
(value) =>
|
|
||||||
allowOfflineAccess.value = value,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 16, bottom: 24),
|
|
||||||
isExpanded: oauthEnabled.value,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: TextButton.icon(
|
|
||||||
onPressed: submitting.value ? null : performAction,
|
|
||||||
label: Text('saveChanges'.tr()),
|
|
||||||
icon: const Icon(Symbols.save),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(all: 24),
|
|
||||||
),
|
),
|
||||||
],
|
).padding(bottom: 32),
|
||||||
),
|
Form(
|
||||||
);
|
key: formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: nameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'name'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: slugController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'slug'.tr(),
|
||||||
|
helperText: 'slugHint'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: descriptionController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'description'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ExpansionPanelList(
|
||||||
|
expansionCallback: (index, isExpanded) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
enableLinks.value = isExpanded;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
oauthEnabled.value = isExpanded;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
ExpansionPanel(
|
||||||
|
headerBuilder: (context, isExpanded) =>
|
||||||
|
ListTile(title: Text('appLinks').tr()),
|
||||||
|
body: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: homePageController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'homePageUrl'.tr(),
|
||||||
|
hintText: 'https://example.com',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: privacyPolicyController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'privacyPolicyUrl'.tr(),
|
||||||
|
hintText: 'https://example.com/privacy',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: termsController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'termsOfServiceUrl'.tr(),
|
||||||
|
hintText: 'https://example.com/terms',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, bottom: 24),
|
||||||
|
isExpanded: enableLinks.value,
|
||||||
|
),
|
||||||
|
ExpansionPanel(
|
||||||
|
headerBuilder: (context, isExpanded) =>
|
||||||
|
ListTile(title: Text('oauthConfig').tr()),
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('redirectUris'.tr()),
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...redirectUris.value.map(
|
||||||
|
(uri) => ListTile(
|
||||||
|
title: Text(uri),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Symbols.delete),
|
||||||
|
onPressed: () {
|
||||||
|
redirectUris.value = redirectUris
|
||||||
|
.value
|
||||||
|
.where((u) => u != uri)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (redirectUris.value.isNotEmpty)
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('addRedirectUri'.tr()),
|
||||||
|
onTap: showAddRedirectUriDialog,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('allowedScopes'.tr()),
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...allowedScopes.value.map(
|
||||||
|
(scope) => ListTile(
|
||||||
|
title: Text(scope),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Symbols.delete),
|
||||||
|
onPressed: () {
|
||||||
|
allowedScopes.value =
|
||||||
|
allowedScopes.value
|
||||||
|
.where((s) => s != scope)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (allowedScopes.value.isNotEmpty)
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('add').tr(),
|
||||||
|
onTap: showAddScopeDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('requirePkce'.tr()),
|
||||||
|
value: requirePkce.value,
|
||||||
|
onChanged: (value) =>
|
||||||
|
requirePkce.value = value,
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('allowOfflineAccess'.tr()),
|
||||||
|
value: allowOfflineAccess.value,
|
||||||
|
onChanged: (value) =>
|
||||||
|
allowOfflineAccess.value = value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, bottom: 24),
|
||||||
|
isExpanded: oauthEnabled.value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: submitting.value ? null : performAction,
|
||||||
|
label: Text('saveChanges'.tr()),
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 24),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (isModal) {
|
if (isModal) {
|
||||||
return bodyContent;
|
return bodyContent;
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ class EditBotScreen extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isNew = id == null;
|
final isNew = id == null;
|
||||||
final botData =
|
final botData = isNew
|
||||||
isNew ? null : ref.watch(botProvider(publisherName, projectId, id!));
|
? null
|
||||||
|
: ref.watch(botProvider(publisherName, projectId, id!));
|
||||||
|
|
||||||
final formKey = useMemoized(() => GlobalKey<FormState>());
|
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||||
final submitting = useState(false);
|
final submitting = useState(false);
|
||||||
@@ -125,14 +126,10 @@ class EditBotScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final cloudFile =
|
final cloudFile = await FileUploader.createCloudFile(
|
||||||
await FileUploader.createCloudFile(
|
ref: ref,
|
||||||
ref: ref,
|
fileData: UniversalFile(data: result, type: UniversalFileType.image),
|
||||||
fileData: UniversalFile(
|
).future;
|
||||||
data: result,
|
|
||||||
type: UniversalFileType.image,
|
|
||||||
),
|
|
||||||
).future;
|
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
}
|
}
|
||||||
@@ -193,284 +190,267 @@ class EditBotScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final bodyContent =
|
final bodyContent = botData == null && !isNew
|
||||||
botData == null && !isNew
|
? const Center(child: CircularProgressIndicator())
|
||||||
? const Center(child: CircularProgressIndicator())
|
: botData?.hasError == true && !isNew
|
||||||
: botData?.hasError == true && !isNew
|
? ResponseErrorWidget(
|
||||||
? ResponseErrorWidget(
|
error: botData!.error,
|
||||||
error: botData!.error,
|
onRetry: () =>
|
||||||
onRetry:
|
ref.invalidate(botProvider(publisherName, projectId, id!)),
|
||||||
() => ref.invalidate(
|
)
|
||||||
botProvider(publisherName, projectId, id!),
|
: SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
)
|
children: [
|
||||||
: SingleChildScrollView(
|
AspectRatio(
|
||||||
child: Column(
|
aspectRatio: 16 / 7,
|
||||||
children: [
|
child: Stack(
|
||||||
AspectRatio(
|
clipBehavior: Clip.none,
|
||||||
aspectRatio: 16 / 7,
|
fit: StackFit.expand,
|
||||||
child: Stack(
|
children: [
|
||||||
clipBehavior: Clip.none,
|
GestureDetector(
|
||||||
fit: StackFit.expand,
|
child: Container(
|
||||||
children: [
|
color: Theme.of(
|
||||||
GestureDetector(
|
context,
|
||||||
child: Container(
|
).colorScheme.surfaceContainerHigh,
|
||||||
color:
|
child: background.value != null
|
||||||
Theme.of(
|
? CloudFileWidget(
|
||||||
context,
|
item: background.value!,
|
||||||
).colorScheme.surfaceContainerHigh,
|
fit: BoxFit.cover,
|
||||||
child:
|
)
|
||||||
background.value != null
|
: const SizedBox.shrink(),
|
||||||
? CloudFileWidget(
|
),
|
||||||
item: background.value!,
|
onTap: () {
|
||||||
fit: BoxFit.cover,
|
setPicture('background');
|
||||||
)
|
},
|
||||||
: const SizedBox.shrink(),
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 20,
|
||||||
|
bottom: -32,
|
||||||
|
child: GestureDetector(
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: picture.value,
|
||||||
|
radius: 40,
|
||||||
|
fallbackIcon: Symbols.smart_toy,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setPicture('background');
|
setPicture('picture');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Positioned(
|
),
|
||||||
left: 20,
|
],
|
||||||
bottom: -32,
|
|
||||||
child: GestureDetector(
|
|
||||||
child: ProfilePictureWidget(
|
|
||||||
fileId: picture.value?.id,
|
|
||||||
radius: 40,
|
|
||||||
fallbackIcon: Symbols.smart_toy,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
setPicture('picture');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).padding(bottom: 32),
|
|
||||||
Form(
|
|
||||||
key: formKey,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: nameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'name'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: nickController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'nickname'.tr(),
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: slugController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'slug'.tr(),
|
|
||||||
helperText: 'slugHint'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: bioController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'bio'.tr(),
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: firstNameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'firstName'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: middleNameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'middleName'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: lastNameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'lastName'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: genderController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'gender'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: pronounsController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'pronouns'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: locationController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'location'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: timeZoneController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'timeZone'.tr(),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
final date = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: birthday.value ?? DateTime.now(),
|
|
||||||
firstDate: DateTime(1900),
|
|
||||||
lastDate: DateTime.now(),
|
|
||||||
);
|
|
||||||
if (date != null) {
|
|
||||||
birthday.value = date;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'birthday'.tr(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
birthday.value != null
|
|
||||||
? DateFormat.yMMMd().format(
|
|
||||||
birthday.value!,
|
|
||||||
)
|
|
||||||
: 'Select a date'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: TextButton.icon(
|
|
||||||
onPressed: submitting.value ? null : performAction,
|
|
||||||
label: Text('saveChanges').tr(),
|
|
||||||
icon: const Icon(Symbols.save),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(all: 24),
|
|
||||||
),
|
),
|
||||||
],
|
).padding(bottom: 32),
|
||||||
),
|
Form(
|
||||||
);
|
key: formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: nameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'name'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: nickController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'nickname'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: slugController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'slug'.tr(),
|
||||||
|
helperText: 'slugHint'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: bioController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'bio'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: firstNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'firstName'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: middleNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'middleName'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: lastNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'lastName'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: genderController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'gender'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: pronounsController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'pronouns'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: locationController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'location'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: timeZoneController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'timeZone'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final date = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: birthday.value ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(1900),
|
||||||
|
lastDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
if (date != null) {
|
||||||
|
birthday.value = date;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'birthday'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
birthday.value != null
|
||||||
|
? DateFormat.yMMMd().format(birthday.value!)
|
||||||
|
: 'Select a date'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: submitting.value ? null : performAction,
|
||||||
|
label: Text('saveChanges').tr(),
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 24),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (isModal) {
|
if (isModal) {
|
||||||
return bodyContent;
|
return bodyContent;
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ class DeveloperSelector extends HookConsumerWidget {
|
|||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
fileId: item.publisher?.picture?.id,
|
file: item.publisher?.picture,
|
||||||
),
|
),
|
||||||
title: Text(item.publisher!.nick),
|
title: Text(item.publisher!.nick),
|
||||||
subtitle: Text('@${item.publisher!.name}'),
|
subtitle: Text('@${item.publisher!.name}'),
|
||||||
@@ -348,7 +348,7 @@ class DeveloperSelector extends HookConsumerWidget {
|
|||||||
if (isReadOnly || currentDeveloper == null) {
|
if (isReadOnly || currentDeveloper == null) {
|
||||||
return ProfilePictureWidget(
|
return ProfilePictureWidget(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
fileId: currentDeveloper?.publisher?.picture?.id,
|
file: currentDeveloper?.publisher?.picture,
|
||||||
).center().padding(right: 8);
|
).center().padding(right: 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ class DeveloperSelector extends HookConsumerWidget {
|
|||||||
...developersMenu.map(
|
...developersMenu.map(
|
||||||
(e) => ProfilePictureWidget(
|
(e) => ProfilePictureWidget(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
fileId: e.value?.publisher?.picture?.id,
|
file: e.value?.publisher?.picture,
|
||||||
).center().padding(right: 8),
|
).center().padding(right: 8),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -928,7 +928,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
|
|||||||
final publisher = items[index];
|
final publisher = items[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: publisher.picture?.id,
|
file: publisher.picture,
|
||||||
fallbackIcon: Symbols.group,
|
fallbackIcon: Symbols.group,
|
||||||
),
|
),
|
||||||
title: Text(publisher.nick),
|
title: Text(publisher.nick),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class SkeletonNotificationTile extends StatelessWidget {
|
|||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
leading: fakePfp != null
|
leading: fakePfp != null
|
||||||
? ProfilePictureWidget(fileId: fakePfp, radius: 20)
|
? ProfilePictureWidget(file: null, radius: 20)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
|
|||||||
@@ -34,16 +34,14 @@ class ArticleEditScreen extends HookConsumerWidget {
|
|||||||
final post = ref.watch(postProvider(id));
|
final post = ref.watch(postProvider(id));
|
||||||
return post.when(
|
return post.when(
|
||||||
data: (post) => ArticleComposeScreen(originalPost: post),
|
data: (post) => ArticleComposeScreen(originalPost: post),
|
||||||
loading:
|
loading: () => AppScaffold(
|
||||||
() => AppScaffold(
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
appBar: AppBar(leading: const PageBackButton()),
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
body: const Center(child: CircularProgressIndicator()),
|
),
|
||||||
),
|
error: (e, _) => AppScaffold(
|
||||||
error:
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
(e, _) => AppScaffold(
|
body: Text('Error: $e', textAlign: TextAlign.center),
|
||||||
appBar: AppBar(leading: const PageBackButton()),
|
),
|
||||||
body: Text('Error: $e', textAlign: TextAlign.center),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,8 +125,8 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
final mostRecentDraft = drafts.values.reduce(
|
final mostRecentDraft = drafts.values.reduce(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
(a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0))
|
(a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0))
|
||||||
? a
|
? a
|
||||||
: b,
|
: b,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only load if the draft has meaningful content
|
// Only load if the draft has meaningful content
|
||||||
@@ -191,12 +189,11 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
MarkdownTextContent(
|
MarkdownTextContent(
|
||||||
content: contentValue.text,
|
content: contentValue.text,
|
||||||
textStyle: theme.textTheme.bodyMedium,
|
textStyle: theme.textTheme.bodyMedium,
|
||||||
attachments:
|
attachments: state.attachments.value
|
||||||
state.attachments.value
|
.where((e) => e.isOnCloud)
|
||||||
.where((e) => e.isOnCloud)
|
.map((e) => e.data)
|
||||||
.map((e) => e.data)
|
.cast<SnCloudFile>()
|
||||||
.cast<SnCloudFile>()
|
.toList(),
|
||||||
.toList(),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -290,22 +287,21 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
onExpansionChanged: (expanded) {
|
onExpansionChanged: (expanded) {
|
||||||
isAttachmentsExpanded.value = expanded;
|
isAttachmentsExpanded.value = expanded;
|
||||||
},
|
},
|
||||||
collapsedBackgroundColor:
|
collapsedBackgroundColor: Theme.of(
|
||||||
Theme.of(context).colorScheme.surfaceContainer,
|
context,
|
||||||
|
).colorScheme.surfaceContainer,
|
||||||
title: Column(
|
title: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('attachments').tr(),
|
Text('attachments').tr(),
|
||||||
Text(
|
Text(
|
||||||
'articleAttachmentHint'.tr(),
|
'articleAttachmentHint'.tr(),
|
||||||
style: Theme.of(
|
style: Theme.of(context).textTheme.bodySmall
|
||||||
context,
|
?.copyWith(
|
||||||
).textTheme.bodySmall?.copyWith(
|
color: Theme.of(
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
context,
|
||||||
).colorScheme.onSurfaceVariant,
|
).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -336,13 +332,12 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
>(
|
>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) =>
|
||||||
(context) =>
|
AttachmentUploaderSheet(
|
||||||
AttachmentUploaderSheet(
|
ref: ref,
|
||||||
ref: ref,
|
state: state,
|
||||||
state: state,
|
index: idx,
|
||||||
index: idx,
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
await ComposeLogic.uploadAttachment(
|
await ComposeLogic.uploadAttachment(
|
||||||
@@ -353,21 +348,20 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUpdate:
|
onUpdate: (value) =>
|
||||||
(value) =>
|
ComposeLogic.updateAttachment(
|
||||||
ComposeLogic.updateAttachment(
|
state,
|
||||||
state,
|
value,
|
||||||
value,
|
idx,
|
||||||
idx,
|
),
|
||||||
),
|
onDelete: () =>
|
||||||
onDelete:
|
ComposeLogic.deleteAttachment(
|
||||||
() => ComposeLogic.deleteAttachment(
|
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
idx,
|
idx,
|
||||||
),
|
),
|
||||||
onInsert:
|
onInsert: () =>
|
||||||
() => ComposeLogic.insertAttachment(
|
ComposeLogic.insertAttachment(
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
idx,
|
idx,
|
||||||
@@ -413,12 +407,11 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
const SizedBox.shrink(),
|
const SizedBox.shrink(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: ProfilePictureWidget(
|
icon: ProfilePictureWidget(
|
||||||
fileId: state.currentPublisher.value?.picture?.id,
|
file: state.currentPublisher.value?.picture,
|
||||||
radius: 12,
|
radius: 12,
|
||||||
fallbackIcon:
|
fallbackIcon: state.currentPublisher.value == null
|
||||||
state.currentPublisher.value == null
|
? Symbols.question_mark
|
||||||
? Symbols.question_mark
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -448,30 +441,26 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
valueListenable: state.submitting,
|
valueListenable: state.submitting,
|
||||||
builder: (context, submitting, _) {
|
builder: (context, submitting, _) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed:
|
onPressed: submitting
|
||||||
submitting
|
? null
|
||||||
? null
|
: () => ComposeLogic.performAction(
|
||||||
: () => ComposeLogic.performAction(
|
ref,
|
||||||
ref,
|
state,
|
||||||
state,
|
context,
|
||||||
context,
|
originalPost: originalPost,
|
||||||
originalPost: originalPost,
|
),
|
||||||
),
|
icon: submitting
|
||||||
icon:
|
? SizedBox(
|
||||||
submitting
|
width: 28,
|
||||||
? SizedBox(
|
height: 28,
|
||||||
width: 28,
|
child: const CircularProgressIndicator(
|
||||||
height: 28,
|
color: Colors.white,
|
||||||
child: const CircularProgressIndicator(
|
strokeWidth: 2.5,
|
||||||
color: Colors.white,
|
|
||||||
strokeWidth: 2.5,
|
|
||||||
),
|
|
||||||
).center()
|
|
||||||
: Icon(
|
|
||||||
originalPost != null
|
|
||||||
? Symbols.edit
|
|
||||||
: Symbols.upload,
|
|
||||||
),
|
),
|
||||||
|
).center()
|
||||||
|
: Icon(
|
||||||
|
originalPost != null ? Symbols.edit : Symbols.upload,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -483,23 +472,22 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
child:
|
child: isWideScreen(context)
|
||||||
isWideScreen(context)
|
? Row(
|
||||||
? Row(
|
spacing: 16,
|
||||||
spacing: 16,
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
Expanded(
|
flex: showPreview.value ? 1 : 2,
|
||||||
flex: showPreview.value ? 1 : 2,
|
child: buildEditorPane(),
|
||||||
child: buildEditorPane(),
|
),
|
||||||
),
|
if (showPreview.value) const VerticalDivider(),
|
||||||
if (showPreview.value) const VerticalDivider(),
|
if (showPreview.value)
|
||||||
if (showPreview.value)
|
Expanded(child: buildPreviewPane()),
|
||||||
Expanded(child: buildPreviewPane()),
|
],
|
||||||
],
|
)
|
||||||
)
|
: showPreview.value
|
||||||
: showPreview.value
|
? buildPreviewPane()
|
||||||
? buildPreviewPane()
|
: buildEditorPane(),
|
||||||
: buildEditorPane(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class _PublisherBasisWidget extends StatelessWidget {
|
|||||||
return Card(
|
return Card(
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final hasBackground = data.background?.id != null;
|
final hasBackground = data.background != null;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -598,7 +598,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
flexibleSpace: Stack(
|
flexibleSpace: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: data.background?.id != null
|
child: data.background != null
|
||||||
? CloudImageWidget(file: data.background)
|
? CloudImageWidget(file: data.background)
|
||||||
: Container(
|
: Container(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
|
|||||||
@@ -183,8 +183,8 @@ class RealmDetailScreen extends HookConsumerWidget {
|
|||||||
flexibleSpace: Stack(
|
flexibleSpace: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: realm!.background?.id != null
|
child: realm!.background != null
|
||||||
? CloudImageWidget(fileId: realm.background!.id)
|
? CloudImageWidget(file: realm.background!)
|
||||||
: Container(
|
: Container(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
@@ -281,8 +281,8 @@ class RealmDetailScreen extends HookConsumerWidget {
|
|||||||
flexibleSpace: Stack(
|
flexibleSpace: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: realm!.background?.id != null
|
child: realm!.background != null
|
||||||
? CloudImageWidget(fileId: realm.background!.id)
|
? CloudImageWidget(file: realm.background!)
|
||||||
: Container(
|
: Container(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
@@ -604,7 +604,7 @@ class _RealmMemberListSheet extends HookConsumerWidget {
|
|||||||
leading: AccountPfcGestureDetector(
|
leading: AccountPfcGestureDetector(
|
||||||
uname: member.account!.name,
|
uname: member.account!.name,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: member.account!.profile.picture?.id,
|
file: member.account!.profile.picture,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Row(
|
title: Row(
|
||||||
|
|||||||
@@ -90,14 +90,10 @@ class EditRealmScreen extends HookConsumerWidget {
|
|||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
try {
|
try {
|
||||||
final cloudFile =
|
final cloudFile = await FileUploader.createCloudFile(
|
||||||
await FileUploader.createCloudFile(
|
ref: ref,
|
||||||
ref: ref,
|
fileData: UniversalFile(data: result, type: UniversalFileType.image),
|
||||||
fileData: UniversalFile(
|
).future;
|
||||||
data: result,
|
|
||||||
type: UniversalFileType.image,
|
|
||||||
),
|
|
||||||
).future;
|
|
||||||
if (cloudFile == null) {
|
if (cloudFile == null) {
|
||||||
throw ArgumentError('Failed to upload the file...');
|
throw ArgumentError('Failed to upload the file...');
|
||||||
}
|
}
|
||||||
@@ -162,13 +158,12 @@ class EditRealmScreen extends HookConsumerWidget {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child:
|
child: background.value != null
|
||||||
background.value != null
|
? CloudFileWidget(
|
||||||
? CloudFileWidget(
|
item: background.value!,
|
||||||
item: background.value!,
|
fit: BoxFit.cover,
|
||||||
fit: BoxFit.cover,
|
)
|
||||||
)
|
: const SizedBox.shrink(),
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setPicture('background');
|
setPicture('background');
|
||||||
@@ -179,7 +174,7 @@ class EditRealmScreen extends HookConsumerWidget {
|
|||||||
bottom: -32,
|
bottom: -32,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: picture.value?.id,
|
file: picture.value,
|
||||||
radius: 40,
|
radius: 40,
|
||||||
fallbackIcon: Symbols.group,
|
fallbackIcon: Symbols.group,
|
||||||
),
|
),
|
||||||
@@ -202,15 +197,15 @@ class EditRealmScreen extends HookConsumerWidget {
|
|||||||
labelText: 'slug'.tr(),
|
labelText: 'slug'.tr(),
|
||||||
helperText: 'slugHint'.tr(),
|
helperText: 'slugHint'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
decoration: InputDecoration(labelText: 'name'.tr()),
|
decoration: InputDecoration(labelText: 'name'.tr()),
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
@@ -221,8 +216,8 @@ class EditRealmScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Card(
|
Card(
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ class _RealmInviteSheet extends HookConsumerWidget {
|
|||||||
final invite = items[index];
|
final invite = items[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: invite.realm!.picture?.id,
|
file: invite.realm!.picture,
|
||||||
fallbackIcon: Symbols.group,
|
fallbackIcon: Symbols.group,
|
||||||
),
|
),
|
||||||
title: Text(invite.realm!.name),
|
title: Text(invite.realm!.name),
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class MarketplaceStickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: CloudImageWidget(
|
child: CloudImageWidget(
|
||||||
fileId: sticker.image.id,
|
file: sticker.image,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -24,193 +24,181 @@ class AccountNameplate extends HookConsumerWidget {
|
|||||||
final user = ref.watch(accountProvider(name));
|
final user = ref.watch(accountProvider(name));
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration:
|
decoration: isOutlined
|
||||||
isOutlined
|
? BoxDecoration(
|
||||||
? BoxDecoration(
|
border: Border.all(
|
||||||
border: Border.all(
|
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
color: Theme.of(context).dividerColor,
|
||||||
color: Theme.of(context).dividerColor,
|
),
|
||||||
),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
)
|
||||||
)
|
: null,
|
||||||
: null,
|
|
||||||
margin: padding,
|
margin: padding,
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: user.when(
|
child: user.when(
|
||||||
data:
|
data: (account) => account.profile.background != null
|
||||||
(account) =>
|
? AspectRatio(
|
||||||
account.profile.background != null
|
aspectRatio: 16 / 9,
|
||||||
? AspectRatio(
|
child: Stack(
|
||||||
aspectRatio: 16 / 9,
|
children: [
|
||||||
child: Stack(
|
// Background image
|
||||||
children: [
|
Positioned.fill(
|
||||||
// Background image
|
child: ClipRRect(
|
||||||
Positioned.fill(
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: ClipRRect(
|
child: CloudFileWidget(
|
||||||
borderRadius: BorderRadius.circular(8),
|
item: account.profile.background!,
|
||||||
child: CloudFileWidget(
|
fit: BoxFit.cover,
|
||||||
item: account.profile.background!,
|
),
|
||||||
fit: BoxFit.cover,
|
),
|
||||||
),
|
),
|
||||||
),
|
// Gradient overlay for text readability
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.black.withOpacity(0.8),
|
||||||
|
Colors.black.withOpacity(0.1),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
// Gradient overlay for text readability
|
),
|
||||||
Positioned.fill(
|
),
|
||||||
child: Container(
|
),
|
||||||
decoration: BoxDecoration(
|
// Content positioned at the bottom
|
||||||
borderRadius: BorderRadius.circular(8),
|
Positioned(
|
||||||
gradient: LinearGradient(
|
left: 0,
|
||||||
begin: Alignment.bottomCenter,
|
right: 0,
|
||||||
end: Alignment.topCenter,
|
bottom: 0,
|
||||||
colors: [
|
child: Padding(
|
||||||
Colors.black.withOpacity(0.8),
|
padding: const EdgeInsets.symmetric(
|
||||||
Colors.black.withOpacity(0.1),
|
horizontal: 16.0,
|
||||||
Colors.transparent,
|
vertical: 8.0,
|
||||||
],
|
),
|
||||||
),
|
child: Row(
|
||||||
),
|
children: [
|
||||||
|
// Profile picture (equivalent to leading)
|
||||||
|
ProfilePictureWidget(
|
||||||
|
file: account.profile.picture,
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 16),
|
||||||
// Content positioned at the bottom
|
// Text content (equivalent to title and subtitle)
|
||||||
Positioned(
|
Expanded(
|
||||||
left: 0,
|
child: Column(
|
||||||
right: 0,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
bottom: 0,
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0,
|
|
||||||
vertical: 8.0,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
children: [
|
||||||
// Profile picture (equivalent to leading)
|
AccountName(
|
||||||
ProfilePictureWidget(
|
account: account,
|
||||||
fileId: account.profile.picture?.id,
|
style: TextStyle(
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
const SizedBox(width: 16),
|
color: Colors.white,
|
||||||
// Text content (equivalent to title and subtitle)
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
AccountName(
|
|
||||||
account: account,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'@${account.name}',
|
|
||||||
).textColor(Colors.white70),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
'@${account.name}',
|
||||||
|
).textColor(Colors.white70),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
: Container(
|
],
|
||||||
padding: const EdgeInsets.symmetric(
|
),
|
||||||
horizontal: 16.0,
|
)
|
||||||
vertical: 8.0,
|
: Container(
|
||||||
),
|
padding: const EdgeInsets.symmetric(
|
||||||
decoration:
|
horizontal: 16.0,
|
||||||
isOutlined
|
vertical: 8.0,
|
||||||
? BoxDecoration(
|
),
|
||||||
border: Border.all(
|
decoration: isOutlined
|
||||||
color:
|
? BoxDecoration(
|
||||||
Theme.of(context).colorScheme.outline,
|
border: Border.all(
|
||||||
),
|
color: Theme.of(context).colorScheme.outline,
|
||||||
borderRadius: BorderRadius.circular(12),
|
),
|
||||||
)
|
borderRadius: BorderRadius.circular(12),
|
||||||
: null,
|
)
|
||||||
child: Row(
|
: null,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Profile picture (equivalent to leading)
|
||||||
|
ProfilePictureWidget(file: account.profile.picture),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Text content (equivalent to title and subtitle)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// Profile picture (equivalent to leading)
|
AccountName(
|
||||||
ProfilePictureWidget(
|
account: account,
|
||||||
fileId: account.profile.picture?.id,
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
// Text content (equivalent to title and subtitle)
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
AccountName(
|
|
||||||
account: account,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text('@${account.name}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
Text('@${account.name}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
loading:
|
],
|
||||||
() => Padding(
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16.0,
|
|
||||||
vertical: 8.0,
|
|
||||||
),
|
),
|
||||||
child: Row(
|
loading: () => Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(
|
||||||
// Loading indicator (equivalent to leading)
|
horizontal: 16.0,
|
||||||
const CircularProgressIndicator(),
|
vertical: 8.0,
|
||||||
const SizedBox(width: 16),
|
),
|
||||||
// Loading text content (equivalent to title and subtitle)
|
child: Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: Column(
|
// Loading indicator (equivalent to leading)
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
const CircularProgressIndicator(),
|
||||||
mainAxisSize: MainAxisSize.min,
|
const SizedBox(width: 16),
|
||||||
children: [
|
// Loading text content (equivalent to title and subtitle)
|
||||||
const Text('loading').bold().tr(),
|
Expanded(
|
||||||
const SizedBox(height: 4),
|
child: Column(
|
||||||
const Text('...'),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
],
|
mainAxisSize: MainAxisSize.min,
|
||||||
),
|
children: [
|
||||||
),
|
const Text('loading').bold().tr(),
|
||||||
],
|
const SizedBox(height: 4),
|
||||||
|
const Text('...'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
error:
|
),
|
||||||
(error, stackTrace) => Padding(
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
error: (error, stackTrace) => Padding(
|
||||||
horizontal: 16.0,
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8.0,
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Error icon (equivalent to leading)
|
||||||
|
const Icon(Symbols.error),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
// Error text content (equivalent to title and subtitle)
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('somethingWentWrong').tr().bold(),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(error.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
],
|
||||||
children: [
|
),
|
||||||
// Error icon (equivalent to leading)
|
),
|
||||||
const Icon(Symbols.error),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
// Error text content (equivalent to title and subtitle)
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text('somethingWentWrong').tr().bold(),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(error.toString()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ class AccountPickerSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -74,23 +74,22 @@ class AccountPickerSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return searchResult.when(
|
return searchResult.when(
|
||||||
data:
|
data: (accounts) => ListView.builder(
|
||||||
(accounts) => ListView.builder(
|
itemCount: accounts.length,
|
||||||
itemCount: accounts.length,
|
itemBuilder: (context, index) {
|
||||||
itemBuilder: (context, index) {
|
final account = accounts[index];
|
||||||
final account = accounts[index];
|
return ListTile(
|
||||||
return ListTile(
|
leading: ProfilePictureWidget(
|
||||||
leading: ProfilePictureWidget(
|
file: account.profile.picture,
|
||||||
fileId: account.profile.picture?.id,
|
),
|
||||||
),
|
title: Text(account.nick),
|
||||||
title: Text(account.nick),
|
subtitle: Text('@${account.name}'),
|
||||||
subtitle: Text('@${account.name}'),
|
onTap: () => Navigator.of(context).pop(account),
|
||||||
onTap: () => Navigator.of(context).pop(account),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
loading: () =>
|
||||||
loading:
|
const Center(child: CircularProgressIndicator()),
|
||||||
() => const Center(child: CircularProgressIndicator()),
|
|
||||||
error: (error, stack) => Center(child: Text('Error: $error')),
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -133,10 +133,9 @@ class _ExpandedSection extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(
|
context,
|
||||||
context,
|
).colorScheme.surfaceContainer,
|
||||||
).colorScheme.surfaceContainer,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -144,8 +143,9 @@ class _ExpandedSection extends StatelessWidget {
|
|||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
'Poll',
|
'Poll',
|
||||||
style:
|
style: Theme.of(
|
||||||
Theme.of(context).textTheme.bodySmall,
|
context,
|
||||||
|
).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -160,8 +160,8 @@ class _ExpandedSection extends StatelessWidget {
|
|||||||
await showModalBottomSheet<SnWalletFund>(
|
await showModalBottomSheet<SnWalletFund>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder:
|
builder: (context) =>
|
||||||
(context) => const ComposeFundSheet(),
|
const ComposeFundSheet(),
|
||||||
);
|
);
|
||||||
if (fund != null) {
|
if (fund != null) {
|
||||||
onFundSelected(fund);
|
onFundSelected(fund);
|
||||||
@@ -169,10 +169,9 @@ class _ExpandedSection extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(
|
context,
|
||||||
context,
|
).colorScheme.surfaceContainer,
|
||||||
).colorScheme.surfaceContainer,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -180,8 +179,9 @@ class _ExpandedSection extends StatelessWidget {
|
|||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
'fund'.tr(),
|
'fund'.tr(),
|
||||||
style:
|
style: Theme.of(
|
||||||
Theme.of(context).textTheme.bodySmall,
|
context,
|
||||||
|
).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -192,11 +192,8 @@ class _ExpandedSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
StickerPickerEmbedded(
|
StickerPickerEmbedded(
|
||||||
height: kInputDrawerExpandedHeight,
|
height: kInputDrawerExpandedHeight,
|
||||||
onPick:
|
onPick: (placeholder) =>
|
||||||
(placeholder) => _insertPlaceholder(
|
_insertPlaceholder(messageController, placeholder),
|
||||||
messageController,
|
|
||||||
placeholder,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -373,15 +370,16 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
switchOutCurve: Curves.fastEaseInToSlowEaseOut,
|
switchOutCurve: Curves.fastEaseInToSlowEaseOut,
|
||||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
return SlideTransition(
|
return SlideTransition(
|
||||||
position: Tween<Offset>(
|
position:
|
||||||
begin: const Offset(0, -0.3),
|
Tween<Offset>(
|
||||||
end: Offset.zero,
|
begin: const Offset(0, -0.3),
|
||||||
).animate(
|
end: Offset.zero,
|
||||||
CurvedAnimation(
|
).animate(
|
||||||
parent: animation,
|
CurvedAnimation(
|
||||||
curve: Curves.easeOutCubic,
|
parent: animation,
|
||||||
),
|
curve: Curves.easeOutCubic,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
child: SizeTransition(
|
child: SizeTransition(
|
||||||
sizeFactor: animation,
|
sizeFactor: animation,
|
||||||
axisAlignment: -1.0,
|
axisAlignment: -1.0,
|
||||||
@@ -389,41 +387,40 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child:
|
child: chatSubscribe.isNotEmpty
|
||||||
chatSubscribe.isNotEmpty
|
? Container(
|
||||||
? Container(
|
key: const ValueKey('typing-indicator'),
|
||||||
key: const ValueKey('typing-indicator'),
|
width: double.infinity,
|
||||||
width: double.infinity,
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 12,
|
||||||
horizontal: 12,
|
vertical: 4,
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Symbols.more_horiz,
|
|
||||||
size: 16,
|
|
||||||
).padding(horizontal: 8),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'typingHint'.plural(
|
|
||||||
chatSubscribe.length,
|
|
||||||
args: [
|
|
||||||
chatSubscribe
|
|
||||||
.map((x) => x.nick ?? x.account.nick)
|
|
||||||
.join(', '),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(
|
|
||||||
key: ValueKey('typing-indicator-none'),
|
|
||||||
),
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Symbols.more_horiz,
|
||||||
|
size: 16,
|
||||||
|
).padding(horizontal: 8),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'typingHint'.plural(
|
||||||
|
chatSubscribe.length,
|
||||||
|
args: [
|
||||||
|
chatSubscribe
|
||||||
|
.map((x) => x.nick ?? x.account.nick)
|
||||||
|
.join(', '),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(
|
||||||
|
key: ValueKey('typing-indicator-none'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
@@ -445,41 +442,36 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child:
|
child: attachments.isNotEmpty
|
||||||
attachments.isNotEmpty
|
? SizedBox(
|
||||||
? SizedBox(
|
key: ValueKey('attachments-${attachments.length}'),
|
||||||
key: ValueKey('attachments-${attachments.length}'),
|
height: 180,
|
||||||
height: 180,
|
child: ListView.separated(
|
||||||
child: ListView.separated(
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
scrollDirection: Axis.horizontal,
|
||||||
scrollDirection: Axis.horizontal,
|
itemCount: attachments.length,
|
||||||
itemCount: attachments.length,
|
itemBuilder: (context, idx) {
|
||||||
itemBuilder: (context, idx) {
|
return SizedBox(
|
||||||
return SizedBox(
|
width: 180,
|
||||||
width: 180,
|
child: AttachmentPreview(
|
||||||
child: AttachmentPreview(
|
isCompact: true,
|
||||||
isCompact: true,
|
item: attachments[idx],
|
||||||
item: attachments[idx],
|
progress:
|
||||||
progress:
|
attachmentProgress['chat-upload']?[idx],
|
||||||
attachmentProgress['chat-upload']?[idx],
|
onRequestUpload: () => onUploadAttachment(idx),
|
||||||
onRequestUpload:
|
onDelete: () => onDeleteAttachment(idx),
|
||||||
() => onUploadAttachment(idx),
|
onUpdate: (value) {
|
||||||
onDelete: () => onDeleteAttachment(idx),
|
attachments[idx] = value;
|
||||||
onUpdate: (value) {
|
onAttachmentsChanged(attachments);
|
||||||
attachments[idx] = value;
|
},
|
||||||
onAttachmentsChanged(attachments);
|
onMove: (delta) => onMoveAttachment(idx, delta),
|
||||||
},
|
),
|
||||||
onMove:
|
);
|
||||||
(delta) => onMoveAttachment(idx, delta),
|
},
|
||||||
),
|
separatorBuilder: (_, _) => const Gap(8),
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (_, _) => const Gap(8),
|
|
||||||
),
|
|
||||||
).padding(vertical: 12)
|
|
||||||
: const SizedBox.shrink(
|
|
||||||
key: ValueKey('no-attachments'),
|
|
||||||
),
|
),
|
||||||
|
).padding(vertical: 12)
|
||||||
|
: const SizedBox.shrink(key: ValueKey('no-attachments')),
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -501,66 +493,62 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child:
|
child: selectedPoll != null
|
||||||
selectedPoll != null
|
? Container(
|
||||||
? Container(
|
key: const ValueKey('selected-poll'),
|
||||||
key: const ValueKey('selected-poll'),
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 16,
|
||||||
horizontal: 16,
|
vertical: 8,
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.surfaceContainerHigh,
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.outline.withOpacity(0.2),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
margin: const EdgeInsets.only(
|
|
||||||
left: 8,
|
|
||||||
right: 8,
|
|
||||||
top: 8,
|
|
||||||
bottom: 8,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Symbols.how_to_vote,
|
|
||||||
size: 18,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
selectedPoll!.title ?? 'Poll',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall!
|
|
||||||
.copyWith(fontWeight: FontWeight.w500),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
icon: const Icon(Icons.close, size: 18),
|
|
||||||
onPressed: () => onPollSelected(null),
|
|
||||||
tooltip: 'clear'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(
|
|
||||||
key: ValueKey('no-selected-poll'),
|
|
||||||
),
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHigh,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.2),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(
|
||||||
|
left: 8,
|
||||||
|
right: 8,
|
||||||
|
top: 8,
|
||||||
|
bottom: 8,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Symbols.how_to_vote,
|
||||||
|
size: 18,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
selectedPoll!.title ?? 'Poll',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall!
|
||||||
|
.copyWith(fontWeight: FontWeight.w500),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: const Icon(Icons.close, size: 18),
|
||||||
|
onPressed: () => onPollSelected(null),
|
||||||
|
tooltip: 'clear'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(key: ValueKey('no-selected-poll')),
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -582,93 +570,88 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child:
|
child: selectedFund != null
|
||||||
selectedFund != null
|
? Container(
|
||||||
? Container(
|
key: const ValueKey('selected-fund'),
|
||||||
key: const ValueKey('selected-fund'),
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 16,
|
||||||
horizontal: 16,
|
vertical: 8,
|
||||||
vertical: 8,
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHigh,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.2),
|
||||||
|
width: 1,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color:
|
margin: const EdgeInsets.only(
|
||||||
Theme.of(
|
left: 8,
|
||||||
context,
|
right: 8,
|
||||||
).colorScheme.surfaceContainerHigh,
|
top: 8,
|
||||||
borderRadius: BorderRadius.circular(24),
|
bottom: 8,
|
||||||
border: Border.all(
|
),
|
||||||
color: Theme.of(
|
child: Row(
|
||||||
context,
|
children: [
|
||||||
).colorScheme.outline.withOpacity(0.2),
|
Icon(
|
||||||
width: 1,
|
Symbols.currency_exchange,
|
||||||
|
size: 18,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
const Gap(8),
|
||||||
margin: const EdgeInsets.only(
|
Expanded(
|
||||||
left: 8,
|
child: Column(
|
||||||
right: 8,
|
mainAxisSize: MainAxisSize.min,
|
||||||
top: 8,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
bottom: 8,
|
children: [
|
||||||
),
|
Text(
|
||||||
child: Row(
|
'${selectedFund!.totalAmount.toStringAsFixed(2)} ${selectedFund!.currency}',
|
||||||
children: [
|
style: Theme.of(context)
|
||||||
Icon(
|
.textTheme
|
||||||
Symbols.currency_exchange,
|
.bodySmall!
|
||||||
size: 18,
|
.copyWith(fontWeight: FontWeight.w500),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
maxLines: 1,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
const Gap(8),
|
),
|
||||||
Expanded(
|
if (selectedFund!.message != null)
|
||||||
child: Column(
|
Padding(
|
||||||
mainAxisSize: MainAxisSize.min,
|
padding: const EdgeInsets.only(top: 2),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Text(
|
||||||
children: [
|
selectedFund!.message!,
|
||||||
Text(
|
style: Theme.of(context)
|
||||||
'${selectedFund!.totalAmount.toStringAsFixed(2)} ${selectedFund!.currency}',
|
.textTheme
|
||||||
style: Theme.of(
|
.bodySmall!
|
||||||
context,
|
.copyWith(
|
||||||
).textTheme.bodySmall!.copyWith(
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.w500,
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
if (selectedFund!.message != null)
|
],
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 2),
|
|
||||||
child: Text(
|
|
||||||
selectedFund!.message!,
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.bodySmall!.copyWith(
|
|
||||||
fontSize: 10,
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(
|
),
|
||||||
width: 24,
|
SizedBox(
|
||||||
height: 24,
|
width: 24,
|
||||||
child: IconButton(
|
height: 24,
|
||||||
padding: EdgeInsets.zero,
|
child: IconButton(
|
||||||
icon: const Icon(Icons.close, size: 18),
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () => onFundSelected(null),
|
icon: const Icon(Icons.close, size: 18),
|
||||||
tooltip: 'clear'.tr(),
|
onPressed: () => onFundSelected(null),
|
||||||
),
|
tooltip: 'clear'.tr(),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
)
|
|
||||||
: const SizedBox.shrink(
|
|
||||||
key: ValueKey('no-selected-fund'),
|
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(key: ValueKey('no-selected-fund')),
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -692,59 +675,57 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child:
|
child:
|
||||||
(messageReplyingTo != null ||
|
(messageReplyingTo != null ||
|
||||||
messageForwardingTo != null ||
|
messageForwardingTo != null ||
|
||||||
messageEditingTo != null)
|
messageEditingTo != null)
|
||||||
? Container(
|
? Container(
|
||||||
key: ValueKey(
|
key: ValueKey(
|
||||||
messageReplyingTo?.id ??
|
messageReplyingTo?.id ??
|
||||||
messageForwardingTo?.id ??
|
messageForwardingTo?.id ??
|
||||||
messageEditingTo?.id ??
|
messageEditingTo?.id ??
|
||||||
'action',
|
'action',
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHigh,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.2),
|
||||||
|
width: 1,
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
),
|
||||||
horizontal: 16,
|
margin: const EdgeInsets.only(
|
||||||
vertical: 8,
|
left: 8,
|
||||||
),
|
right: 8,
|
||||||
decoration: BoxDecoration(
|
top: 8,
|
||||||
color:
|
bottom: 8,
|
||||||
Theme.of(
|
),
|
||||||
context,
|
child: Column(
|
||||||
).colorScheme.surfaceContainerHigh,
|
mainAxisSize: MainAxisSize.min,
|
||||||
borderRadius: BorderRadius.circular(24),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
border: Border.all(
|
children: [
|
||||||
color: Theme.of(
|
Row(
|
||||||
context,
|
children: [
|
||||||
).colorScheme.outline.withOpacity(0.2),
|
Icon(
|
||||||
width: 1,
|
messageReplyingTo != null
|
||||||
),
|
? Symbols.reply
|
||||||
),
|
: messageForwardingTo != null
|
||||||
margin: const EdgeInsets.only(
|
? Symbols.forward
|
||||||
left: 8,
|
: Symbols.edit,
|
||||||
right: 8,
|
size: 18,
|
||||||
top: 8,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
bottom: 8,
|
),
|
||||||
),
|
const Gap(8),
|
||||||
child: Column(
|
Expanded(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
messageReplyingTo != null
|
messageReplyingTo != null
|
||||||
? Symbols.reply
|
? 'chatReplyingTo'.tr(
|
||||||
: messageForwardingTo != null
|
|
||||||
? Symbols.forward
|
|
||||||
: Symbols.edit,
|
|
||||||
size: 18,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
messageReplyingTo != null
|
|
||||||
? 'chatReplyingTo'.tr(
|
|
||||||
args: [
|
args: [
|
||||||
messageReplyingTo
|
messageReplyingTo
|
||||||
?.sender
|
?.sender
|
||||||
@@ -753,60 +734,57 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
'unknown'.tr(),
|
'unknown'.tr(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: messageForwardingTo != null
|
: messageForwardingTo != null
|
||||||
? 'chatForwarding'.tr()
|
? 'chatForwarding'.tr()
|
||||||
: 'chatEditing'.tr(),
|
: 'chatEditing'.tr(),
|
||||||
style: Theme.of(
|
style: Theme.of(context)
|
||||||
context,
|
.textTheme
|
||||||
).textTheme.bodySmall!.copyWith(
|
.bodySmall!
|
||||||
fontWeight: FontWeight.w500,
|
.copyWith(fontWeight: FontWeight.w500),
|
||||||
),
|
maxLines: 1,
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
icon: const Icon(Icons.close, size: 18),
|
|
||||||
onPressed: onClear,
|
|
||||||
tooltip: 'clear'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (messageReplyingTo != null ||
|
|
||||||
messageForwardingTo != null ||
|
|
||||||
messageEditingTo != null)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 6,
|
|
||||||
left: 26,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
(messageReplyingTo ??
|
|
||||||
messageForwardingTo ??
|
|
||||||
messageEditingTo)
|
|
||||||
?.content ??
|
|
||||||
'chatNoContent'.tr(),
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.bodySmall!.copyWith(
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SizedBox(
|
||||||
),
|
width: 24,
|
||||||
)
|
height: 24,
|
||||||
: const SizedBox.shrink(key: ValueKey('no-action')),
|
child: IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: const Icon(Icons.close, size: 18),
|
||||||
|
onPressed: onClear,
|
||||||
|
tooltip: 'clear'.tr(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (messageReplyingTo != null ||
|
||||||
|
messageForwardingTo != null ||
|
||||||
|
messageEditingTo != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 6,
|
||||||
|
left: 26,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
(messageReplyingTo ??
|
||||||
|
messageForwardingTo ??
|
||||||
|
messageEditingTo)
|
||||||
|
?.content ??
|
||||||
|
'chatNoContent'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall!
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(key: ValueKey('no-action')),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -815,25 +793,19 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip:
|
tooltip: isExpanded.value
|
||||||
isExpanded.value ? 'collapse'.tr() : 'more'.tr(),
|
? 'collapse'.tr()
|
||||||
|
: 'more'.tr(),
|
||||||
icon: AnimatedSwitcher(
|
icon: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
transitionBuilder:
|
transitionBuilder: (child, animation) =>
|
||||||
(child, animation) => FadeTransition(
|
FadeTransition(opacity: animation, child: child),
|
||||||
opacity: animation,
|
child: isExpanded.value
|
||||||
child: child,
|
? const Icon(
|
||||||
),
|
Symbols.close,
|
||||||
child:
|
key: ValueKey('close'),
|
||||||
isExpanded.value
|
)
|
||||||
? const Icon(
|
: const Icon(Symbols.add, key: ValueKey('add')),
|
||||||
Symbols.close,
|
|
||||||
key: ValueKey('close'),
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Symbols.add,
|
|
||||||
key: ValueKey('add'),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
isExpanded.value = !isExpanded.value;
|
isExpanded.value = !isExpanded.value;
|
||||||
@@ -885,46 +857,43 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
hintMaxLines: 1,
|
hintMaxLines: 1,
|
||||||
hintText:
|
hintText:
|
||||||
(chatRoom.type == 1 && chatRoom.name == null)
|
(chatRoom.type == 1 && chatRoom.name == null)
|
||||||
? 'chatDirectMessageHint'.tr(
|
? 'chatDirectMessageHint'.tr(
|
||||||
args: [
|
args: [
|
||||||
getValidMembers(
|
getValidMembers(
|
||||||
chatRoom.members!,
|
chatRoom.members!,
|
||||||
).map((e) => e.account.nick).join(', '),
|
).map((e) => e.account.nick).join(', '),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: 'chatMessageHint'.tr(
|
: 'chatMessageHint'.tr(args: [chatRoom.name!]),
|
||||||
args: [chatRoom.name!],
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
vertical: 12,
|
vertical: 12,
|
||||||
),
|
),
|
||||||
counterText:
|
counterText: messageController.text.length > 1024
|
||||||
messageController.text.length > 1024
|
? '${messageController.text.length}/4096'
|
||||||
? '${messageController.text.length}/4096'
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) =>
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
textInputAction: settings.enterToSend
|
||||||
textInputAction:
|
? TextInputAction.send
|
||||||
settings.enterToSend
|
: null,
|
||||||
? TextInputAction.send
|
onSubmitted: settings.enterToSend
|
||||||
: null,
|
? (_) => send()
|
||||||
onSubmitted:
|
: null,
|
||||||
settings.enterToSend ? (_) => send() : null,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestionsCallback: (pattern) async {
|
suggestionsCallback: (pattern) async {
|
||||||
// Only trigger on @ or :
|
// Only trigger on @ or :
|
||||||
final atIndex = pattern.lastIndexOf('@');
|
final atIndex = pattern.lastIndexOf('@');
|
||||||
final colonIndex = pattern.lastIndexOf(':');
|
final colonIndex = pattern.lastIndexOf(':');
|
||||||
final triggerIndex =
|
final triggerIndex = atIndex > colonIndex
|
||||||
atIndex > colonIndex ? atIndex : colonIndex;
|
? atIndex
|
||||||
|
: colonIndex;
|
||||||
if (triggerIndex == -1) return [];
|
if (triggerIndex == -1) return [];
|
||||||
final chopped = pattern.substring(triggerIndex);
|
final chopped = pattern.substring(triggerIndex);
|
||||||
if (chopped.contains(' ')) return [];
|
if (chopped.contains(' ')) return [];
|
||||||
@@ -986,9 +955,7 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 28,
|
width: 28,
|
||||||
height: 28,
|
height: 28,
|
||||||
child: CloudImageWidget(
|
child: CloudImageWidget(file: sticker.image),
|
||||||
fileId: sticker.image.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -1005,8 +972,9 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
final text = messageController.text;
|
final text = messageController.text;
|
||||||
final atIndex = text.lastIndexOf('@');
|
final atIndex = text.lastIndexOf('@');
|
||||||
final colonIndex = text.lastIndexOf(':');
|
final colonIndex = text.lastIndexOf(':');
|
||||||
final triggerIndex =
|
final triggerIndex = atIndex > colonIndex
|
||||||
atIndex > colonIndex ? atIndex : colonIndex;
|
? atIndex
|
||||||
|
: colonIndex;
|
||||||
if (triggerIndex == -1) return;
|
if (triggerIndex == -1) return;
|
||||||
final newText = text.replaceRange(
|
final newText = text.replaceRange(
|
||||||
triggerIndex,
|
triggerIndex,
|
||||||
@@ -1053,16 +1021,15 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child:
|
child: isExpanded.value
|
||||||
isExpanded.value
|
? _ExpandedSection(
|
||||||
? _ExpandedSection(
|
messageController: messageController,
|
||||||
messageController: messageController,
|
selectedPoll: selectedPoll,
|
||||||
selectedPoll: selectedPoll,
|
onPollSelected: onPollSelected,
|
||||||
onPollSelected: onPollSelected,
|
selectedFund: selectedFund,
|
||||||
selectedFund: selectedFund,
|
onFundSelected: onFundSelected,
|
||||||
onFundSelected: onFundSelected,
|
)
|
||||||
)
|
: const SizedBox.shrink(key: ValueKey('collapsed')),
|
||||||
: const SizedBox.shrink(key: ValueKey('collapsed')),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class MessageListTile extends StatelessWidget {
|
|||||||
radius: 20,
|
radius: 20,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: sender.account.profile.picture?.id,
|
file: sender.account.profile.picture,
|
||||||
radius: 20,
|
radius: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -24,12 +24,11 @@ class MessageSenderInfo extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final timestamp =
|
final timestamp = DateTime.now().difference(createdAt).inDays > 365
|
||||||
DateTime.now().difference(createdAt).inDays > 365
|
? DateFormat('yyyy/MM/dd HH:mm').format(createdAt.toLocal())
|
||||||
? DateFormat('yyyy/MM/dd HH:mm').format(createdAt.toLocal())
|
: DateTime.now().difference(createdAt).inDays > 0
|
||||||
: DateTime.now().difference(createdAt).inDays > 0
|
? DateFormat('MM/dd HH:mm').format(createdAt.toLocal())
|
||||||
? DateFormat('MM/dd HH:mm').format(createdAt.toLocal())
|
: DateFormat('HH:mm').format(createdAt.toLocal());
|
||||||
: DateFormat('HH:mm').format(createdAt.toLocal());
|
|
||||||
|
|
||||||
if (isCompact) {
|
if (isCompact) {
|
||||||
return Row(
|
return Row(
|
||||||
@@ -41,7 +40,7 @@ class MessageSenderInfo extends StatelessWidget {
|
|||||||
AccountPfcGestureDetector(
|
AccountPfcGestureDetector(
|
||||||
uname: sender.account.name,
|
uname: sender.account.name,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: sender.account.profile.picture?.id,
|
file: sender.account.profile.picture,
|
||||||
radius: 14,
|
radius: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -69,7 +68,7 @@ class MessageSenderInfo extends StatelessWidget {
|
|||||||
AccountPfcGestureDetector(
|
AccountPfcGestureDetector(
|
||||||
uname: sender.account.name,
|
uname: sender.account.name,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: sender.account.profile.picture?.id,
|
file: sender.account.profile.picture,
|
||||||
radius: 14,
|
radius: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -106,7 +105,7 @@ class MessageSenderInfo extends StatelessWidget {
|
|||||||
AccountPfcGestureDetector(
|
AccountPfcGestureDetector(
|
||||||
uname: sender.account.name,
|
uname: sender.account.name,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: sender.account.profile.picture?.id,
|
file: sender.account.profile.picture,
|
||||||
radius: 16,
|
radius: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -99,15 +99,15 @@ class PublicRoomPreview extends HookConsumerWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 26,
|
height: 26,
|
||||||
width: 26,
|
width: 26,
|
||||||
child: (room.type == 1 && room.picture?.id == null)
|
child: (room.type == 1 && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: room.members!
|
files: room.members!
|
||||||
.map((e) => e.account.profile.picture?.id)
|
.map((e) => e.account.profile.picture)
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
)
|
||||||
: room.picture?.id != null
|
: room.picture != null
|
||||||
? ProfilePictureWidget(
|
? ProfilePictureWidget(
|
||||||
fileId: room.picture?.id,
|
file: room.picture,
|
||||||
fallbackIcon: Symbols.chat,
|
fallbackIcon: Symbols.chat,
|
||||||
)
|
)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
@@ -132,15 +132,15 @@ class PublicRoomPreview extends HookConsumerWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 26,
|
height: 26,
|
||||||
width: 26,
|
width: 26,
|
||||||
child: (room.type == 1 && room.picture?.id == null)
|
child: (room.type == 1 && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: room.members!
|
files: room.members!
|
||||||
.map((e) => e.account.profile.picture?.id)
|
.map((e) => e.account.profile.picture)
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
)
|
||||||
: room.picture?.id != null
|
: room.picture != null
|
||||||
? ProfilePictureWidget(
|
? ProfilePictureWidget(
|
||||||
fileId: room.picture?.id,
|
file: room.picture,
|
||||||
fallbackIcon: Symbols.chat,
|
fallbackIcon: Symbols.chat,
|
||||||
)
|
)
|
||||||
: CircleAvatar(
|
: CircleAvatar(
|
||||||
|
|||||||
@@ -22,15 +22,13 @@ class ChatRoomAvatar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final avatarChild = (isDirect && room.picture?.id == null)
|
final avatarChild = (isDirect && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: validMembers
|
files: validMembers.map((e) => e.account.profile.picture).toList(),
|
||||||
.map((e) => e.account.profile.picture?.id)
|
|
||||||
.toList(),
|
|
||||||
)
|
)
|
||||||
: room.picture?.id == null
|
: room.picture == null
|
||||||
? CircleAvatar(child: Text((room.name ?? 'DM')[0].toUpperCase()))
|
? CircleAvatar(child: Text((room.name ?? 'DM')[0].toUpperCase()))
|
||||||
: ProfilePictureWidget(fileId: room.picture?.id);
|
: ProfilePictureWidget(file: room.picture);
|
||||||
|
|
||||||
final badgeChild = Badge(
|
final badgeChild = Badge(
|
||||||
isLabelVisible: summary.when(
|
isLabelVisible: summary.when(
|
||||||
|
|||||||
@@ -262,10 +262,7 @@ class CheckInActivityWidget extends StatelessWidget {
|
|||||||
spacing: 12,
|
spacing: 12,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(file: result.account!.profile.picture, radius: 12),
|
||||||
fileId: result.account!.profile.picture?.id,
|
|
||||||
radius: 12,
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
|||||||
@@ -577,6 +577,9 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final String? id = file?.id ?? fileId;
|
final String? id = file?.id ?? fileId;
|
||||||
|
|
||||||
|
final meta = file?.fileMeta is Map ? (file!.fileMeta as Map) : const {};
|
||||||
|
final blurHash = meta['blur'] as String?;
|
||||||
|
|
||||||
final fallback = Icon(
|
final fallback = Icon(
|
||||||
fallbackIcon ?? Symbols.account_circle,
|
fallbackIcon ?? Symbols.account_circle,
|
||||||
size: radius,
|
size: radius,
|
||||||
@@ -590,6 +593,7 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
placeholder: fallback,
|
placeholder: fallback,
|
||||||
content: () => UniversalImage(
|
content: () => UniversalImage(
|
||||||
uri: '$serverUrl/drive/files/$id',
|
uri: '$serverUrl/drive/files/$id',
|
||||||
|
blurHash: blurHash,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -625,14 +629,14 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SplitAvatarWidget extends ConsumerWidget {
|
class SplitAvatarWidget extends ConsumerWidget {
|
||||||
final List<String?> filesId;
|
final List<SnCloudFile?> files;
|
||||||
final double radius;
|
final double radius;
|
||||||
final IconData fallbackIcon;
|
final IconData fallbackIcon;
|
||||||
final Color? fallbackColor;
|
final Color? fallbackColor;
|
||||||
|
|
||||||
const SplitAvatarWidget({
|
const SplitAvatarWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.filesId,
|
required this.files,
|
||||||
this.radius = 20,
|
this.radius = 20,
|
||||||
this.fallbackIcon = Symbols.account_circle,
|
this.fallbackIcon = Symbols.account_circle,
|
||||||
this.fallbackColor,
|
this.fallbackColor,
|
||||||
@@ -640,17 +644,17 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
if (filesId.isEmpty) {
|
if (files.isEmpty) {
|
||||||
return ProfilePictureWidget(
|
return ProfilePictureWidget(
|
||||||
fileId: null,
|
file: null,
|
||||||
radius: radius,
|
radius: radius,
|
||||||
fallbackIcon: fallbackIcon,
|
fallbackIcon: fallbackIcon,
|
||||||
fallbackColor: fallbackColor,
|
fallbackColor: fallbackColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (filesId.length == 1) {
|
if (files.length == 1) {
|
||||||
return ProfilePictureWidget(
|
return ProfilePictureWidget(
|
||||||
fileId: filesId[0],
|
file: files[0],
|
||||||
radius: radius,
|
radius: radius,
|
||||||
fallbackIcon: fallbackIcon,
|
fallbackIcon: fallbackIcon,
|
||||||
fallbackColor: fallbackColor,
|
fallbackColor: fallbackColor,
|
||||||
@@ -665,32 +669,32 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (filesId.length == 2)
|
if (files.length == 2)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(context, filesId[0], ref, radius),
|
child: _buildQuadrant(context, files[0], ref, radius),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(context, filesId[1], ref, radius),
|
child: _buildQuadrant(context, files[1], ref, radius),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
else if (filesId.length == 3)
|
else if (files.length == 3)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(context, filesId[0], ref, radius),
|
child: _buildQuadrant(context, files[0], ref, radius),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(context, filesId[1], ref, radius),
|
child: _buildQuadrant(context, files[1], ref, radius),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(context, filesId[2], ref, radius),
|
child: _buildQuadrant(context, files[2], ref, radius),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -701,20 +705,10 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(
|
child: _buildQuadrant(context, files[0], ref, radius),
|
||||||
context,
|
|
||||||
filesId[0],
|
|
||||||
ref,
|
|
||||||
radius,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(
|
child: _buildQuadrant(context, files[1], ref, radius),
|
||||||
context,
|
|
||||||
filesId[1],
|
|
||||||
ref,
|
|
||||||
radius,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -723,22 +717,17 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuadrant(
|
child: _buildQuadrant(context, files[2], ref, radius),
|
||||||
context,
|
|
||||||
filesId[2],
|
|
||||||
ref,
|
|
||||||
radius,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: filesId.length > 4
|
child: files.length > 4
|
||||||
? Container(
|
? Container(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
).colorScheme.primaryContainer,
|
).colorScheme.primaryContainer,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'+${filesId.length - 3}',
|
'+${files.length - 3}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: radius * 0.4,
|
fontSize: radius * 0.4,
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
@@ -748,12 +737,7 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: _buildQuadrant(
|
: _buildQuadrant(context, files[3], ref, radius),
|
||||||
context,
|
|
||||||
filesId[3],
|
|
||||||
ref,
|
|
||||||
radius,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -768,11 +752,11 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
|
|
||||||
Widget _buildQuadrant(
|
Widget _buildQuadrant(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String? fileId,
|
SnCloudFile? file,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
double radius,
|
double radius,
|
||||||
) {
|
) {
|
||||||
if (fileId == null) {
|
if (file == null) {
|
||||||
return Container(
|
return Container(
|
||||||
width: radius,
|
width: radius,
|
||||||
height: radius,
|
height: radius,
|
||||||
@@ -787,7 +771,7 @@ class SplitAvatarWidget extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final uri = '$serverUrl/drive/files/$fileId';
|
final uri = '$serverUrl/drive/files/${file.id}';
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: radius,
|
width: radius,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
import 'package:flutter_blurhash/flutter_blurhash.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class UniversalImage extends StatelessWidget {
|
class UniversalImage extends HookWidget {
|
||||||
final String uri;
|
final String uri;
|
||||||
final String? blurHash;
|
final String? blurHash;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
@@ -27,6 +28,7 @@ class UniversalImage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loaded = useState(false);
|
||||||
final isSvgImage = isSvg || uri.toLowerCase().endsWith('.svg');
|
final isSvgImage = isSvg || uri.toLowerCase().endsWith('.svg');
|
||||||
|
|
||||||
if (isSvgImage) {
|
if (isSvgImage) {
|
||||||
@@ -35,9 +37,8 @@ class UniversalImage extends StatelessWidget {
|
|||||||
fit: fit,
|
fit: fit,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
placeholderBuilder:
|
placeholderBuilder: (BuildContext context) =>
|
||||||
(BuildContext context) =>
|
Center(child: CircularProgressIndicator()),
|
||||||
Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +47,9 @@ class UniversalImage extends StatelessWidget {
|
|||||||
if (width != null && height != null && !noCacheOptimization) {
|
if (width != null && height != null && !noCacheOptimization) {
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
cacheWidth = width != null ? (width! * devicePixelRatio).round() : null;
|
cacheWidth = width != null ? (width! * devicePixelRatio).round() : null;
|
||||||
cacheHeight =
|
cacheHeight = height != null
|
||||||
height != null ? (height! * devicePixelRatio).round() : null;
|
? (height! * devicePixelRatio).round()
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@@ -66,21 +68,72 @@ class UniversalImage extends StatelessWidget {
|
|||||||
memCacheWidth: cacheWidth,
|
memCacheWidth: cacheWidth,
|
||||||
progressIndicatorBuilder: (context, url, progress) {
|
progressIndicatorBuilder: (context, url, progress) {
|
||||||
return Center(
|
return Center(
|
||||||
child: CircularProgressIndicator(value: progress.progress),
|
child: AnimatedCircularProgressIndicator(
|
||||||
|
value: progress.progress,
|
||||||
|
color: Colors.white.withOpacity(0.5),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
errorWidget:
|
imageBuilder: (context, imageProvider) {
|
||||||
(context, url, error) =>
|
Future(() => loaded.value = true);
|
||||||
useFallbackImage
|
return AnimatedOpacity(
|
||||||
? Image.asset(
|
opacity: loaded.value ? 1.0 : 0.0,
|
||||||
'assets/images/media-offline.jpg',
|
duration: const Duration(milliseconds: 300),
|
||||||
fit: BoxFit.cover,
|
child: Image(
|
||||||
key: Key('image-broke-$uri'),
|
image: imageProvider,
|
||||||
)
|
fit: fit,
|
||||||
: SizedBox.shrink(),
|
width: width,
|
||||||
|
height: height,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorWidget: (context, url, error) => useFallbackImage
|
||||||
|
? Image.asset(
|
||||||
|
'assets/images/media-offline.jpg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
key: Key('image-broke-$uri'),
|
||||||
|
)
|
||||||
|
: SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AnimatedCircularProgressIndicator extends HookWidget {
|
||||||
|
final double? value;
|
||||||
|
final Color? color;
|
||||||
|
final double strokeWidth;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
const AnimatedCircularProgressIndicator({
|
||||||
|
super.key,
|
||||||
|
this.value,
|
||||||
|
this.color,
|
||||||
|
this.strokeWidth = 4.0,
|
||||||
|
this.duration = const Duration(milliseconds: 200),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final animationController = useAnimationController(duration: duration);
|
||||||
|
final animation = useAnimation(
|
||||||
|
Tween<double>(begin: 0.0, end: value ?? 0.0).animate(
|
||||||
|
CurvedAnimation(parent: animationController, curve: Curves.linear),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
animationController.animateTo(value ?? 0.0);
|
||||||
|
return null;
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return CircularProgressIndicator(
|
||||||
|
value: animation,
|
||||||
|
color: color,
|
||||||
|
strokeWidth: strokeWidth,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,12 +41,11 @@ class ComposeFormFields extends HookConsumerWidget {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: onPublisherTap,
|
onTap: onPublisherTap,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: state.currentPublisher.value?.picture?.id,
|
file: state.currentPublisher.value?.picture,
|
||||||
radius: 20,
|
radius: 20,
|
||||||
fallbackIcon:
|
fallbackIcon: state.currentPublisher.value == null
|
||||||
state.currentPublisher.value == null
|
? Icons.question_mark
|
||||||
? Icons.question_mark
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -98,8 +97,8 @@ class ComposeFormFields extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Description field
|
// Description field
|
||||||
@@ -115,8 +114,8 @@ class ComposeFormFields extends HookConsumerWidget {
|
|||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Content field
|
// Content field
|
||||||
@@ -138,16 +137,17 @@ class ComposeFormFields extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
suggestionsCallback: (pattern) async {
|
suggestionsCallback: (pattern) async {
|
||||||
// Only trigger on @ or :
|
// Only trigger on @ or :
|
||||||
final atIndex = pattern.lastIndexOf('@');
|
final atIndex = pattern.lastIndexOf('@');
|
||||||
final colonIndex = pattern.lastIndexOf(':');
|
final colonIndex = pattern.lastIndexOf(':');
|
||||||
final triggerIndex =
|
final triggerIndex = atIndex > colonIndex
|
||||||
atIndex > colonIndex ? atIndex : colonIndex;
|
? atIndex
|
||||||
|
: colonIndex;
|
||||||
if (triggerIndex == -1) return [];
|
if (triggerIndex == -1) return [];
|
||||||
final chopped = pattern.substring(triggerIndex);
|
final chopped = pattern.substring(triggerIndex);
|
||||||
if (chopped.contains(' ')) return [];
|
if (chopped.contains(' ')) return [];
|
||||||
@@ -202,7 +202,7 @@ class ComposeFormFields extends HookConsumerWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 28,
|
width: 28,
|
||||||
height: 28,
|
height: 28,
|
||||||
child: CloudImageWidget(fileId: sticker.image.id),
|
child: CloudImageWidget(file: sticker.image),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -219,8 +219,9 @@ class ComposeFormFields extends HookConsumerWidget {
|
|||||||
final text = state.contentController.text;
|
final text = state.contentController.text;
|
||||||
final atIndex = text.lastIndexOf('@');
|
final atIndex = text.lastIndexOf('@');
|
||||||
final colonIndex = text.lastIndexOf(':');
|
final colonIndex = text.lastIndexOf(':');
|
||||||
final triggerIndex =
|
final triggerIndex = atIndex > colonIndex
|
||||||
atIndex > colonIndex ? atIndex : colonIndex;
|
? atIndex
|
||||||
|
: colonIndex;
|
||||||
if (triggerIndex == -1) return;
|
if (triggerIndex == -1) return;
|
||||||
final newText = text.replaceRange(
|
final newText = text.replaceRange(
|
||||||
triggerIndex,
|
triggerIndex,
|
||||||
@@ -281,8 +282,8 @@ class ArticleComposeFormFields extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Description field
|
// Description field
|
||||||
@@ -297,8 +298,8 @@ class ArticleComposeFormFields extends StatelessWidget {
|
|||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Content field (expanded)
|
// Content field (expanded)
|
||||||
@@ -317,8 +318,8 @@ class ArticleComposeFormFields extends StatelessWidget {
|
|||||||
maxLines: null,
|
maxLines: null,
|
||||||
expands: true,
|
expands: true,
|
||||||
textAlignVertical: TextAlignVertical.top,
|
textAlignVertical: TextAlignVertical.top,
|
||||||
onTapOutside:
|
onTapOutside: (_) =>
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -135,10 +135,7 @@ class CompactReferencePost extends StatelessWidget {
|
|||||||
Widget _buildProfilePicture(BuildContext context) {
|
Widget _buildProfilePicture(BuildContext context) {
|
||||||
// Handle publisher case
|
// Handle publisher case
|
||||||
if (post.publisher != null) {
|
if (post.publisher != null) {
|
||||||
return ProfilePictureWidget(
|
return ProfilePictureWidget(file: post.publisher!.picture, radius: 16);
|
||||||
fileId: post.publisher!.picture?.id,
|
|
||||||
radius: 16,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// Handle actor case
|
// Handle actor case
|
||||||
if (post.actor != null) {
|
if (post.actor != null) {
|
||||||
@@ -169,7 +166,7 @@ class CompactReferencePost extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback
|
// Fallback
|
||||||
return ProfilePictureWidget(fileId: null, radius: 16);
|
return ProfilePictureWidget(file: null, radius: 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getDisplayName() {
|
String _getDisplayName() {
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
fileId: currentRealm.picture?.id,
|
file: currentRealm.picture,
|
||||||
fallbackIcon: Symbols.workspaces,
|
fallbackIcon: Symbols.workspaces,
|
||||||
radius: 16,
|
radius: 16,
|
||||||
),
|
),
|
||||||
@@ -428,7 +428,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
fileId: realm.picture?.id,
|
file: realm.picture,
|
||||||
fallbackIcon: Symbols.workspaces,
|
fallbackIcon: Symbols.workspaces,
|
||||||
radius: 16,
|
radius: 16,
|
||||||
),
|
),
|
||||||
@@ -454,7 +454,7 @@ class ComposeSettingsSheet extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
fileId: currentRealm.picture?.id,
|
file: currentRealm.picture,
|
||||||
fallbackIcon: Symbols.workspaces,
|
fallbackIcon: Symbols.workspaces,
|
||||||
radius: 16,
|
radius: 16,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class PostAwardSheet extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback
|
// Fallback
|
||||||
return ProfilePictureWidget(fileId: null, radius: radius);
|
return ProfilePictureWidget(file: null, radius: radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getPublisherName() {
|
String _getPublisherName() {
|
||||||
|
|||||||
@@ -73,96 +73,94 @@ class PostQuickReply extends HookConsumerWidget {
|
|||||||
const kInputChipHeight = 54.0;
|
const kInputChipHeight = 54.0;
|
||||||
|
|
||||||
return publishers.when(
|
return publishers.when(
|
||||||
data:
|
data: (data) => Material(
|
||||||
(data) => Material(
|
elevation: 2,
|
||||||
elevation: 2,
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
borderRadius: BorderRadius.circular(28),
|
||||||
borderRadius: BorderRadius.circular(28),
|
child: Container(
|
||||||
child: Container(
|
constraints: BoxConstraints(minHeight: kInputChipHeight),
|
||||||
constraints: BoxConstraints(minHeight: kInputChipHeight),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
child: Row(
|
||||||
child: Row(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
GestureDetector(
|
||||||
GestureDetector(
|
child: ProfilePictureWidget(
|
||||||
child: ProfilePictureWidget(
|
file: currentPublisher.value?.picture,
|
||||||
fileId: currentPublisher.value?.picture?.id,
|
radius: (kInputChipHeight * 0.5) - 6,
|
||||||
radius: (kInputChipHeight * 0.5) - 6,
|
),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PublisherModal(),
|
||||||
|
).then((value) {
|
||||||
|
if (value is SnPublisher) {
|
||||||
|
currentPublisher.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
).padding(right: 12),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: contentController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'postReplyPlaceholder'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 14,
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
isScrollControlled: true,
|
|
||||||
context: context,
|
|
||||||
builder: (context) => PublisherModal(),
|
|
||||||
).then((value) {
|
|
||||||
if (value is SnPublisher) {
|
|
||||||
currentPublisher.value = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
).padding(right: 12),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: contentController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'postReplyPlaceholder'.tr(),
|
|
||||||
border: InputBorder.none,
|
|
||||||
isDense: true,
|
|
||||||
isCollapsed: true,
|
|
||||||
contentPadding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 14,
|
|
||||||
),
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
),
|
|
||||||
style: TextStyle(fontSize: 14),
|
|
||||||
minLines: 1,
|
|
||||||
maxLines: 5,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
onLaunch?.call();
|
|
||||||
final value = await PostComposeSheet.show(
|
|
||||||
context,
|
|
||||||
initialState: PostComposeInitialState(
|
|
||||||
content: contentController.text,
|
|
||||||
replyingTo: parent,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (value != null) onPosted?.call();
|
|
||||||
},
|
|
||||||
icon: const Icon(Symbols.launch, size: 20),
|
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxHeight: kInputChipHeight - 6,
|
|
||||||
minHeight: kInputChipHeight - 6,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
IconButton(
|
style: TextStyle(fontSize: 14),
|
||||||
icon:
|
minLines: 1,
|
||||||
submitting.value
|
maxLines: 5,
|
||||||
? SizedBox(
|
onTapOutside: (_) =>
|
||||||
width: 28,
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
height: 28,
|
),
|
||||||
child: CircularProgressIndicator(strokeWidth: 3),
|
|
||||||
)
|
|
||||||
: Icon(Symbols.send, size: 20),
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
onPressed: submitting.value ? null : performAction,
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxHeight: kInputChipHeight - 6,
|
|
||||||
minHeight: kInputChipHeight - 6,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const Gap(8),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
onLaunch?.call();
|
||||||
|
final value = await PostComposeSheet.show(
|
||||||
|
context,
|
||||||
|
initialState: PostComposeInitialState(
|
||||||
|
content: contentController.text,
|
||||||
|
replyingTo: parent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (value != null) onPosted?.call();
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.launch, size: 20),
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: kInputChipHeight - 6,
|
||||||
|
minHeight: kInputChipHeight - 6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: submitting.value
|
||||||
|
? SizedBox(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 3),
|
||||||
|
)
|
||||||
|
: Icon(Symbols.send, size: 20),
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
onPressed: submitting.value ? null : performAction,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: kInputChipHeight - 6,
|
||||||
|
minHeight: kInputChipHeight - 6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
error: (e, _) => const SizedBox.shrink(),
|
error: (e, _) => const SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class PostReplyPreview extends HookConsumerWidget {
|
|||||||
return ActorPictureWidget(actor: post.actor!, radius: radius);
|
return ActorPictureWidget(actor: post.actor!, radius: radius);
|
||||||
}
|
}
|
||||||
// Fallback
|
// Fallback
|
||||||
return ProfilePictureWidget(fileId: null, radius: radius);
|
return ProfilePictureWidget(file: null, radius: radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -448,7 +448,7 @@ class ReferencedPostWidget extends StatelessWidget {
|
|||||||
// Handle publisher case
|
// Handle publisher case
|
||||||
if (post.publisher != null) {
|
if (post.publisher != null) {
|
||||||
return ProfilePictureWidget(
|
return ProfilePictureWidget(
|
||||||
fileId: post.publisher!.picture?.id,
|
file: post.publisher!.picture,
|
||||||
radius: radius,
|
radius: radius,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -457,7 +457,7 @@ class ReferencedPostWidget extends StatelessWidget {
|
|||||||
return ActorPictureWidget(actor: post.actor!, radius: radius);
|
return ActorPictureWidget(actor: post.actor!, radius: radius);
|
||||||
}
|
}
|
||||||
// Fallback
|
// Fallback
|
||||||
return ProfilePictureWidget(fileId: null, radius: radius);
|
return ProfilePictureWidget(file: null, radius: radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getDisplayName(SnPost post) {
|
String _getDisplayName(SnPost post) {
|
||||||
@@ -701,7 +701,7 @@ class PostHeader extends HookConsumerWidget {
|
|||||||
return ActorPictureWidget(actor: post.actor!, radius: radius);
|
return ActorPictureWidget(actor: post.actor!, radius: radius);
|
||||||
}
|
}
|
||||||
// Fallback
|
// Fallback
|
||||||
return ProfilePictureWidget(fileId: null, radius: radius);
|
return ProfilePictureWidget(file: null, radius: radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getDisplayName(SnPost post) {
|
String _getDisplayName(SnPost post) {
|
||||||
|
|||||||
@@ -21,63 +21,57 @@ class PublisherModal extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: publishers.when(
|
child: publishers.when(
|
||||||
data:
|
data: (value) => value.isEmpty
|
||||||
(value) =>
|
? ConstrainedBox(
|
||||||
value.isEmpty
|
constraints: BoxConstraints(maxWidth: 280),
|
||||||
? ConstrainedBox(
|
child: Column(
|
||||||
constraints: BoxConstraints(maxWidth: 280),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
child:
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
'publishersEmpty',
|
||||||
children: [
|
textAlign: TextAlign.center,
|
||||||
Text(
|
).tr().fontSize(17).bold(),
|
||||||
'publishersEmpty',
|
Text(
|
||||||
textAlign: TextAlign.center,
|
'publishersEmptyDescription',
|
||||||
).tr().fontSize(17).bold(),
|
textAlign: TextAlign.center,
|
||||||
Text(
|
).tr(),
|
||||||
'publishersEmptyDescription',
|
const Gap(12),
|
||||||
textAlign: TextAlign.center,
|
ElevatedButton(
|
||||||
).tr(),
|
onPressed: () {
|
||||||
const Gap(12),
|
showModalBottomSheet(
|
||||||
ElevatedButton(
|
context: context,
|
||||||
onPressed: () {
|
isScrollControlled: true,
|
||||||
showModalBottomSheet(
|
builder: (context) =>
|
||||||
context: context,
|
const NewPublisherScreen(),
|
||||||
isScrollControlled: true,
|
).then((value) {
|
||||||
builder:
|
if (value != null) {
|
||||||
(context) =>
|
ref.invalidate(publishersManagedProvider);
|
||||||
const NewPublisherScreen(),
|
}
|
||||||
).then((value) {
|
});
|
||||||
if (value != null) {
|
},
|
||||||
ref.invalidate(
|
child: Text('createPublisher').tr(),
|
||||||
publishersManagedProvider,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text('createPublisher').tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).center(),
|
|
||||||
)
|
|
||||||
: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
for (final publisher in value)
|
|
||||||
ListTile(
|
|
||||||
leading: ProfilePictureWidget(
|
|
||||||
fileId: publisher.picture?.id,
|
|
||||||
),
|
|
||||||
title: Text(publisher.nick),
|
|
||||||
subtitle: Text('@${publisher.name}'),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.pop(context, publisher);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
).center(),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (final publisher in value)
|
||||||
|
ListTile(
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
file: publisher.picture,
|
||||||
|
),
|
||||||
|
title: Text(publisher.nick),
|
||||||
|
subtitle: Text('@${publisher.name}'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context, publisher);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (e, _) => Text('Error: $e'),
|
error: (e, _) => Text('Error: $e'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -30,17 +30,16 @@ class RealmListTile extends StatelessWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child:
|
child: realm.background == null
|
||||||
realm.background == null
|
? const SizedBox.shrink()
|
||||||
? const SizedBox.shrink()
|
: CloudImageWidget(file: realm.background),
|
||||||
: CloudImageWidget(file: realm.background),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: -30,
|
bottom: -30,
|
||||||
left: 18,
|
left: 18,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
fileId: realm.picture?.id,
|
file: realm.picture,
|
||||||
fallbackIcon: Symbols.group,
|
fallbackIcon: Symbols.group,
|
||||||
radius: 24,
|
radius: 24,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class RealmSelectionDropdown extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ProfilePictureWidget(
|
ProfilePictureWidget(
|
||||||
fileId: realm.picture?.id,
|
file: realm.picture,
|
||||||
fallbackIcon: Symbols.workspaces,
|
fallbackIcon: Symbols.workspaces,
|
||||||
radius: 16,
|
radius: 16,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -776,19 +776,19 @@ class _ChatRoomOption extends HookConsumerWidget {
|
|||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: (isDirect && room.picture?.id == null)
|
child: (isDirect && room.picture == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId: validMembers
|
files: validMembers
|
||||||
.map((e) => e.account.profile.picture?.id)
|
.map((e) => e.account.profile.picture)
|
||||||
.toList(),
|
.toList(),
|
||||||
radius: 16,
|
radius: 16,
|
||||||
)
|
)
|
||||||
: room.picture?.id == null
|
: room.picture == null
|
||||||
? CircleAvatar(
|
? CircleAvatar(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
child: Text(room.name![0].toUpperCase()),
|
child: Text(room.name![0].toUpperCase()),
|
||||||
)
|
)
|
||||||
: ProfilePictureWidget(fileId: room.picture?.id, radius: 16),
|
: ProfilePictureWidget(file: room.picture, radius: 16),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
// Chat room name
|
// Chat room name
|
||||||
|
|||||||
@@ -75,31 +75,29 @@ class StickerPicker extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading:
|
loading: () => const SizedBox(
|
||||||
() => const SizedBox(
|
width: 320,
|
||||||
width: 320,
|
height: 320,
|
||||||
height: 320,
|
child: Center(child: CircularProgressIndicator()),
|
||||||
child: Center(child: CircularProgressIndicator()),
|
),
|
||||||
),
|
error: (err, _) => SizedBox(
|
||||||
error:
|
width: 360,
|
||||||
(err, _) => SizedBox(
|
height: 200,
|
||||||
width: 360,
|
child: Column(
|
||||||
height: 200,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
const Icon(Symbols.error, size: 28),
|
||||||
children: [
|
const Gap(8),
|
||||||
const Icon(Symbols.error, size: 28),
|
Text('Error: $err', textAlign: TextAlign.center),
|
||||||
const Gap(8),
|
const Gap(12),
|
||||||
Text('Error: $err', textAlign: TextAlign.center),
|
FilledButton.icon(
|
||||||
const Gap(12),
|
onPressed: () => ref.invalidate(myStickerPacksProvider),
|
||||||
FilledButton.icon(
|
icon: const Icon(Symbols.refresh),
|
||||||
onPressed: () => ref.invalidate(myStickerPacksProvider),
|
label: Text('retry').tr(),
|
||||||
icon: const Icon(Symbols.refresh),
|
),
|
||||||
label: Text('retry').tr(),
|
],
|
||||||
),
|
).padding(all: 16),
|
||||||
],
|
),
|
||||||
).padding(all: 16),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -263,7 +261,7 @@ class _StickersGrid extends StatelessWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: CloudImageWidget(
|
child: CloudImageWidget(
|
||||||
fileId: sticker.image.id,
|
file: sticker.image,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -310,31 +308,29 @@ class StickerPickerEmbedded extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading:
|
loading: () => SizedBox(
|
||||||
() => SizedBox(
|
width: 320,
|
||||||
width: 320,
|
height: height ?? 320,
|
||||||
height: height ?? 320,
|
child: const Center(child: CircularProgressIndicator()),
|
||||||
child: const Center(child: CircularProgressIndicator()),
|
),
|
||||||
),
|
error: (err, _) => SizedBox(
|
||||||
error:
|
width: 360,
|
||||||
(err, _) => SizedBox(
|
height: height ?? 200,
|
||||||
width: 360,
|
child: Column(
|
||||||
height: height ?? 200,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
const Icon(Symbols.error, size: 28),
|
||||||
children: [
|
const Gap(8),
|
||||||
const Icon(Symbols.error, size: 28),
|
Text('Error: $err', textAlign: TextAlign.center),
|
||||||
const Gap(8),
|
const Gap(12),
|
||||||
Text('Error: $err', textAlign: TextAlign.center),
|
FilledButton.icon(
|
||||||
const Gap(12),
|
onPressed: () => ref.invalidate(myStickerPacksProvider),
|
||||||
FilledButton.icon(
|
icon: const Icon(Symbols.refresh),
|
||||||
onPressed: () => ref.invalidate(myStickerPacksProvider),
|
label: Text('retry').tr(),
|
||||||
icon: const Icon(Symbols.refresh),
|
),
|
||||||
label: Text('retry').tr(),
|
],
|
||||||
),
|
).padding(all: 16),
|
||||||
],
|
),
|
||||||
).padding(all: 16),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,18 +382,16 @@ class _EmbeddedPackSwitcherState extends State<_EmbeddedPackSwitcher> {
|
|||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: selected
|
||||||
selected
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
: Theme.of(context).colorScheme.surfaceContainer,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
border:
|
border: selected
|
||||||
selected
|
? Border.all(
|
||||||
? Border.all(
|
color: Theme.of(context).colorScheme.primary,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
width: 4,
|
||||||
width: 4,
|
)
|
||||||
)
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.only(right: 8),
|
margin: const EdgeInsets.only(right: 8),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
@@ -413,11 +407,11 @@ class _EmbeddedPackSwitcherState extends State<_EmbeddedPackSwitcher> {
|
|||||||
builder: (context, value, _) {
|
builder: (context, value, _) {
|
||||||
return packs[i].icon != null
|
return packs[i].icon != null
|
||||||
? CloudImageWidget(
|
? CloudImageWidget(
|
||||||
file: packs[i].icon!,
|
file: packs[i].icon!,
|
||||||
).clipRRect(all: value)
|
).clipRRect(all: value)
|
||||||
: CloudImageWidget(
|
: CloudImageWidget(
|
||||||
file: packs[i].stickers.firstOrNull?.image,
|
file: packs[i].stickers.firstOrNull?.image,
|
||||||
).clipRRect(all: value);
|
).clipRRect(all: value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -458,18 +452,17 @@ Future<void> showStickerPickerPopover(
|
|||||||
offset: offset,
|
offset: offset,
|
||||||
alignment: alignment ?? Alignment.topLeft,
|
alignment: alignment ?? Alignment.topLeft,
|
||||||
dimBackground: true,
|
dimBackground: true,
|
||||||
builder:
|
builder: (ctx) => SizedBox(
|
||||||
(ctx) => SizedBox(
|
width: math.min(480, MediaQuery.of(context).size.width * 0.9),
|
||||||
width: math.min(480, MediaQuery.of(context).size.width * 0.9),
|
height: 480,
|
||||||
height: 480,
|
child: ProviderScope(
|
||||||
child: ProviderScope(
|
child: StickerPicker(
|
||||||
child: StickerPicker(
|
onPick: (ph) {
|
||||||
onPick: (ph) {
|
onPick(ph);
|
||||||
onPick(ph);
|
Navigator.of(ctx).maybePop();
|
||||||
Navigator.of(ctx).maybePop();
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user