use single file to manage all colors

This commit is contained in:
otsmr 2026-10-04 21:32:19 +02:00
parent 75df8840ae
commit 8580908536
138 changed files with 1183 additions and 873 deletions

View file

@ -14,11 +14,17 @@ import 'package:twonly/src/model/error_code.dart';
import 'package:twonly/src/providers/settings.provider.dart';
import 'package:twonly/src/services/backup.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/themes/colors.dart';
export 'package:twonly/src/visual/themes/colors.dart' show AppColor;
extension ShortCutsExtension on BuildContext {
AppLocalizations get lang => AppLocalizations.of(this)!;
ColorScheme get color => Theme.of(this).colorScheme;
Color appColor(AppColor color) {
return Theme.of(this).extension<AppColors>()![color];
}
Future<dynamic> navPush(Widget route) async {
return Navigator.push(
this,

View file

@ -60,7 +60,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
decoration: BoxDecoration(
color: Colors.red.withAlpha(100),
color: context.color.errorContainer,
borderRadius: BorderRadius.circular(10),
),
child: Column(
@ -71,7 +71,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
textAlign: TextAlign.center,
softWrap: true,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.white,
color: context.color.onErrorContainer,
fontSize: 16,
),
),
@ -90,7 +90,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
decoration: BoxDecoration(
color: Colors.red,
color: context.color.error,
borderRadius: BorderRadius.circular(10),
),
child: Column(
@ -101,7 +101,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
textAlign: TextAlign.center,
softWrap: true,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.white,
color: context.color.onError,
fontSize: 16,
),
),
@ -123,7 +123,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
child: Text(
context.lang.appOutdatedBtn,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.white,
color: context.color.onSurface,
fontSize: 16,
),
),

View file

@ -17,9 +17,9 @@ class ConnectionStatusComp extends StatelessWidget {
return Stack(
children: [
if (!isConnected)
const Positioned.fill(
Positioned.fill(
child: SpinKitRipple(
color: Colors.red,
color: context.color.error,
),
),
Container(
@ -29,7 +29,7 @@ class ConnectionStatusComp extends StatelessWidget {
border: Border.all(
color: isConnected
? context.color.primary.withAlpha(100)
: Colors.red,
: context.color.error,
),
),
padding: const EdgeInsets.all(0.5),

View file

@ -34,13 +34,11 @@ class ContactRequestBadgeComp extends StatelessWidget {
),
Center(
child: NotificationBadgeComp(
backgroundColor: isDarkMode(context)
? Colors.white
: Colors.black,
textColor: isDarkMode(context) ? Colors.black : Colors.white,
backgroundColor: context.color.inverseSurface,
textColor: context.color.onInverseSurface,
count: count.toString(),
child: IconButton(
color: Colors.black,
color: context.color.onPrimary,
icon: const FaIcon(
FontAwesomeIcons.userPlus,
size: 18,

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/misc.dart';
typedef TextLabelBuilder = Widget Function(String label);
@ -99,14 +100,9 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
child: ValueListenableBuilder<double>(
valueListenable: _thumbOffsetNotifier,
builder: (context, thumbOffset, child) {
final isDark =
Theme.of(context).brightness == Brightness.dark;
final handleColor = isDark
? Colors.grey.shade900
: Colors.white;
final iconColor = isDark
? Colors.white70
: Colors.black54;
final colors = context.color;
final handleColor = colors.surfaceContainerLowest;
final iconColor = colors.onSurfaceVariant;
final label = widget.labelBuilder?.call(
_viewOffsetNotifier.value,
@ -145,17 +141,14 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
horizontal: 12,
),
decoration: BoxDecoration(
color:
(isDark
? Colors.grey.shade900
: Colors.grey.shade200)
color: colors.surfaceContainerHigh
.withValues(alpha: 0.95),
borderRadius: BorderRadius.circular(
8,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(
color: colors.shadow.withValues(
alpha: 0.2,
),
blurRadius: 8,
@ -166,9 +159,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
child: Text(
label,
style: TextStyle(
color: isDark
? Colors.white
: Colors.black,
color: colors.onSurface,
fontSize: 14,
fontWeight: FontWeight.bold,
),
@ -182,9 +173,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
decoration: BoxDecoration(
color: handleColor,
border: Border.all(
color: isDark
? Colors.white10
: Colors.black12,
color: colors.outlineVariant,
width: 0.5,
),
borderRadius: const BorderRadius.only(

View file

@ -33,7 +33,7 @@ class EmojiPickerBottom extends StatelessWidget {
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.grey,
color: context.color.outline,
),
height: 3,
width: 60,
@ -66,7 +66,7 @@ class EmojiPickerBottom extends StatelessWidget {
),
searchViewConfig: SearchViewConfig(
backgroundColor: context.color.surfaceContainer,
buttonIconColor: Colors.white,
buttonIconColor: context.color.onSurface,
),
categoryViewConfig: CategoryViewConfig(
backgroundColor: context.color.surfaceContainer,

View file

@ -1,16 +1,17 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
class NotificationBadgeComp extends StatelessWidget {
const NotificationBadgeComp({
required this.count,
required this.child,
this.backgroundColor = Colors.red,
this.textColor = Colors.white,
this.backgroundColor,
this.textColor,
super.key,
});
final String count;
final Color backgroundColor;
final Color textColor;
final Color? backgroundColor;
final Color? textColor;
final Widget child;
@override
@ -27,14 +28,14 @@ class NotificationBadgeComp extends StatelessWidget {
height: 18,
width: 18,
child: CircleAvatar(
backgroundColor: backgroundColor,
backgroundColor: backgroundColor ?? context.color.error,
child: Center(
child: Transform.rotate(
angle: infinity ? 90 * (3.141592653589793 / 180) : 0,
child: Text(
infinity ? '8' : count,
style: TextStyle(
color: textColor,
color: textColor ?? context.color.onError,
fontSize: 10,
),
),

View file

@ -143,13 +143,13 @@ class _ProfileQrCodeCompState extends State<ProfileQrCodeComp> {
? Container(
key: const ValueKey('qr_code_container'),
decoration: BoxDecoration(
color: context.color.primary,
color: context.appColor(AppColor.qrBackground),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Colors.black26,
color: context.color.shadow.withValues(alpha: 0.2),
blurRadius: 3,
offset: Offset(0, 2),
offset: const Offset(0, 2),
),
],
),
@ -159,11 +159,11 @@ class _ProfileQrCodeCompState extends State<ProfileQrCodeComp> {
errorCorrectLevel: QrErrorCorrectLevel.M,
),
eyeStyle: QrEyeStyle(
color: isDarkMode(context) ? Colors.black : Colors.white,
color: context.appColor(AppColor.qrForeground),
borderRadius: 2,
),
dataModuleStyle: QrDataModuleStyle(
color: isDarkMode(context) ? Colors.black : Colors.white,
color: context.appColor(AppColor.qrForeground),
borderRadius: 2,
),
gapless: false,
@ -173,7 +173,7 @@ class _ProfileQrCodeCompState extends State<ProfileQrCodeComp> {
embeddedImageStyle: QrEmbeddedImageStyle(
size: const Size(60, 66),
embeddedImageShape: EmbeddedImageShape.square,
shapeColor: context.color.primary,
shapeColor: context.appColor(AppColor.qrBackground),
safeArea: true,
),
size: widget.size,

View file

@ -68,7 +68,7 @@ class _SelectChatDeletionTimeListTitleState
bottom: MediaQuery.of(context).viewInsets.bottom,
),
// Provide a background color for the popup.
color: CupertinoColors.systemBackground.resolveFrom(context),
color: context.color.surface,
// Use a SafeArea widget to avoid system overlaps.
child: SafeArea(top: false, child: child),
),

View file

@ -20,11 +20,11 @@ class SelectableThumbnailComp extends StatelessWidget {
curve: Curves.easeInOut,
decoration: BoxDecoration(
color: isSelected ? context.color.primary : Colors.transparent,
boxShadow: const [
boxShadow: [
BoxShadow(
color: Colors.black12,
color: context.color.shadow.withValues(alpha: 0.12),
blurRadius: 4,
offset: Offset(0, 2),
offset: const Offset(0, 2),
),
],
),
@ -49,20 +49,20 @@ class SelectableThumbnailComp extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: isSelected ? context.color.primary : Colors.black38,
color: isSelected
? context.color.primary
: context.color.scrim.withValues(alpha: 0.38),
shape: BoxShape.circle,
border: Border.all(
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black,
color: context.color.onSurface,
width: 1.5,
),
),
child: isSelected
? const Icon(
? Icon(
Icons.check,
size: 14,
color: Colors.white,
color: context.color.onPrimary,
)
: const SizedBox(width: 14, height: 14),
),

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
enum SnackbarLevel {
info,
@ -14,23 +15,28 @@ void showSnackbar(
String message, {
SnackbarLevel level = SnackbarLevel.error,
}) {
Color backgroundColor;
IconData iconData;
switch (level) {
case SnackbarLevel.info:
backgroundColor = Colors.blue.shade700;
iconData = Icons.info_outline;
case SnackbarLevel.success:
backgroundColor = Colors.green.shade700;
iconData = Icons.check_circle_outline;
case SnackbarLevel.warning:
backgroundColor = Colors.orange.shade800;
iconData = Icons.warning_amber_rounded;
case SnackbarLevel.error:
backgroundColor = Colors.red.shade700;
iconData = Icons.error_outline;
}
final (backgroundColor, foregroundColor, iconData) = switch (level) {
SnackbarLevel.info => (
context.appColor(AppColor.info),
context.appColor(AppColor.onInfo),
Icons.info_outline,
),
SnackbarLevel.success => (
context.appColor(AppColor.success),
context.appColor(AppColor.onSuccess),
Icons.check_circle_outline,
),
SnackbarLevel.warning => (
context.appColor(AppColor.warning),
context.appColor(AppColor.onWarning),
Icons.warning_amber_rounded,
),
SnackbarLevel.error => (
context.color.error,
context.color.onError,
Icons.error_outline,
),
};
AnimationController? localAnimationController;
@ -44,7 +50,8 @@ void showSnackbar(
child: _SnackbarWidget(
message: message,
backgroundColor: backgroundColor,
icon: Icon(iconData, color: Colors.white, size: 28),
foregroundColor: foregroundColor,
icon: Icon(iconData, color: foregroundColor, size: 28),
onCloseClick: () {
localAnimationController?.reverse();
},
@ -101,11 +108,13 @@ class _SnackbarWidget extends StatelessWidget {
const _SnackbarWidget({
required this.message,
required this.backgroundColor,
required this.foregroundColor,
required this.icon,
required this.onCloseClick,
});
final String message;
final Color backgroundColor;
final Color foregroundColor;
final Icon icon;
final VoidCallback onCloseClick;
@ -118,9 +127,9 @@ class _SnackbarWidget extends StatelessWidget {
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Colors.black26,
color: Theme.of(context).colorScheme.shadow.withValues(alpha: 0.2),
spreadRadius: 1,
blurRadius: 30,
),
@ -138,10 +147,10 @@ class _SnackbarWidget extends StatelessWidget {
child: Text(
message,
style: theme.textTheme.bodyMedium?.merge(
const TextStyle(
TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white,
color: foregroundColor,
),
),
textAlign: TextAlign.start,
@ -151,9 +160,13 @@ class _SnackbarWidget extends StatelessWidget {
GestureDetector(
onTap: onCloseClick,
behavior: HitTestBehavior.opaque,
child: const Padding(
padding: EdgeInsets.all(16),
child: Icon(Icons.close, color: Colors.white70, size: 20),
child: Padding(
padding: const EdgeInsets.all(16),
child: Icon(
Icons.close,
color: foregroundColor.withValues(alpha: 0.75),
size: 20,
),
),
),
],

View file

@ -7,7 +7,7 @@ import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/daos/key_verification.dao.dart';
import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/visual/components/verification_badge_info.comp.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
class VerificationBadgeComp extends StatefulWidget {
@ -198,7 +198,7 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
? SvgIcons.verifiedNumeric(_verifiedByTransferredTrustCount)
: SvgIcons.verifiedRed,
color: (_verifiedByTransferredTrustCount > 0 && !_isVerified)
? colorVerificationBadgeYellow
? context.appColor(AppColor.verificationPending)
: null,
size: widget.size,
),

View file

@ -10,8 +10,6 @@ import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
import 'package:twonly/src/visual/elements/my_button.element.dart';
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
const colorVerificationBadgeYellow = Color.fromARGB(255, 0, 182, 238);
class VerificationBadgeInfo extends StatelessWidget {
const VerificationBadgeInfo({
this.displayButtons = false,
@ -121,19 +119,19 @@ class VerificationBadgeInfo extends StatelessWidget {
),
_buildItem(
context,
icon: const SvgIcon(
icon: SvgIcon(
assetPath: SvgIcons.verifiedGreen,
size: 40,
color: colorVerificationBadgeYellow,
color: context.appColor(AppColor.verificationPending),
),
description: context.lang.verificationBadgeYellowDesc,
boldTextColor: colorVerificationBadgeYellow,
boldTextColor: context.appColor(AppColor.verificationPending),
),
_buildItem(
context,
icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40),
description: context.lang.verificationBadgeRedDesc,
boldTextColor: const Color(0xffff0000),
boldTextColor: context.color.error,
),
],
);

View file

@ -3,7 +3,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:twonly/src/visual/themes/light.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:vector_graphics/vector_graphics.dart';
/// Animated chain-link logo for the "verification success" moment.
@ -85,9 +85,6 @@ class VerificationSuccessAnimationState
static const _svg2 =
'<svg viewBox="0 0 640 640"><path d="$_path2" fill="white"/></svg>';
static const _grey = Color(0xFF8E9AAF);
static const Color _green = defaultPrimaryColor;
@override
void initState() {
super.initState();
@ -196,6 +193,7 @@ class VerificationSuccessAnimationState
animation: Listenable.merge([_ctrl, _idleCtrl]),
builder: (context, _) {
final scale = widget.size / 640;
final successColor = context.color.primary;
final t = _flyIn.value; // 0 → 1 (fly-in progress)
final sep = 1.0 - t; // 1 → 0 (separation)
@ -209,7 +207,11 @@ class VerificationSuccessAnimationState
final rotation = sep * 0.7;
// Interpolated colour.
final color = Color.lerp(_grey, _green, _colorFade.value)!;
final color = Color.lerp(
context.appColor(AppColor.verificationIdle),
successColor,
_colorFade.value,
)!;
// Pulse: 1 → 1.08 → 1 (smooth sine bump).
final pulseScale = 1.0 + math.sin(_pulse.value * math.pi) * 0.08;
@ -232,7 +234,9 @@ class VerificationSuccessAnimationState
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: _green.withValues(alpha: 0.3 * _glow.value),
color: successColor.withValues(
alpha: 0.3 * _glow.value,
),
blurRadius: widget.size * 0.45 * _glow.value,
spreadRadius: widget.size * 0.08 * _glow.value,
),
@ -247,7 +251,7 @@ class VerificationSuccessAnimationState
child: CustomPaint(
painter: _RipplePainter(
progress: _ripple.value,
color: _green,
color: successColor,
maxRadius: widget.size * 0.45,
),
),
@ -323,7 +327,7 @@ class VerificationSuccessAnimationState
child: CustomPaint(
painter: _SparklePainter(
progress: _sparkle.value,
color: _green,
color: successColor,
maxRadius: widget.size * 0.4,
),
),

View file

@ -20,7 +20,10 @@ InputDecoration inputTextMessageDeco(BuildContext context, String hintText) {
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: const BorderSide(color: Colors.grey, width: 2),
borderSide: BorderSide(
color: Theme.of(context).colorScheme.outline,
width: 2,
),
),
);
}

View file

@ -90,9 +90,9 @@ class _BetterTextState extends State<BetterText> {
spans.add(
TextSpan(
text: url,
style: const TextStyle(
style: TextStyle(
decoration: TextDecoration.underline,
decorationColor: Colors.white,
decorationColor: widget.textColor,
),
recognizer: recognizer,
),

View file

@ -68,9 +68,9 @@ class ContactChip extends StatelessWidget {
],
if (onTap != null && contact != null) ...[
const SizedBox(width: 15),
const FaIcon(
FaIcon(
FontAwesomeIcons.xmark,
color: Colors.grey,
color: context.color.onSurfaceVariant,
size: 12,
),
],

View file

@ -37,20 +37,16 @@ class _MyButtonState extends State<MyButton> {
@override
Widget build(BuildContext context) {
final isEnabled = widget.onPressed != null || widget.onLongPress != null;
final isDark = isDarkMode(context);
final disabledBgColor = isDark
? const Color(0xFF353535)
: const Color(0xFFE0E0E0);
final disabledFgColor = isDark
? const Color(0xFF757575)
: const Color(0xFF9E9E9E);
final colors = context.color;
final disabledBgColor = colors.surfaceContainerHighest;
final disabledFgColor = colors.onSurface.withValues(alpha: 0.38);
late final ButtonStyle buttonStyle;
switch (widget.variant) {
case MyButtonVariant.primary:
buttonStyle = FilledButton.styleFrom(
backgroundColor: context.color.primary,
foregroundColor: Colors.black87,
foregroundColor: colors.onPrimary,
disabledBackgroundColor: disabledBgColor,
disabledForegroundColor: disabledFgColor,
minimumSize: const Size.fromHeight(60),
@ -65,8 +61,8 @@ class _MyButtonState extends State<MyButton> {
);
case MyButtonVariant.secondary:
buttonStyle = FilledButton.styleFrom(
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
foregroundColor: isDark ? Colors.white : Colors.black87,
backgroundColor: colors.surfaceContainerHigh,
foregroundColor: colors.onSurface,
disabledBackgroundColor: disabledBgColor,
disabledForegroundColor: disabledFgColor,
minimumSize: const Size.fromHeight(60),
@ -82,9 +78,7 @@ class _MyButtonState extends State<MyButton> {
case MyButtonVariant.text:
buttonStyle = TextButton.styleFrom(
minimumSize: const Size(0, 50),
foregroundColor: isDark
? Colors.white.withValues(alpha: 0.7)
: Colors.black.withValues(alpha: 0.7),
foregroundColor: colors.onSurfaceVariant,
disabledForegroundColor: disabledFgColor,
textStyle: const TextStyle(
fontSize: 15,
@ -97,7 +91,7 @@ class _MyButtonState extends State<MyButton> {
case MyButtonVariant.primaryMiddle:
buttonStyle = FilledButton.styleFrom(
backgroundColor: context.color.primary,
foregroundColor: Colors.black87,
foregroundColor: colors.onPrimary,
disabledBackgroundColor: disabledBgColor,
disabledForegroundColor: disabledFgColor,
minimumSize: const Size(0, 48),
@ -116,7 +110,7 @@ class _MyButtonState extends State<MyButton> {
case MyButtonVariant.primaryDense:
buttonStyle = FilledButton.styleFrom(
backgroundColor: context.color.primary,
foregroundColor: Colors.black87,
foregroundColor: colors.onPrimary,
disabledBackgroundColor: disabledBgColor,
disabledForegroundColor: disabledFgColor,
minimumSize: const Size(0, 40),
@ -134,8 +128,8 @@ class _MyButtonState extends State<MyButton> {
);
case MyButtonVariant.secondaryDense:
buttonStyle = FilledButton.styleFrom(
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
foregroundColor: isDark ? Colors.white : Colors.black87,
backgroundColor: colors.surfaceContainerHigh,
foregroundColor: colors.onSurface,
disabledBackgroundColor: disabledBgColor,
disabledForegroundColor: disabledFgColor,
minimumSize: const Size(0, 40),
@ -153,8 +147,8 @@ class _MyButtonState extends State<MyButton> {
);
case MyButtonVariant.secondaryMiddle:
buttonStyle = FilledButton.styleFrom(
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
foregroundColor: isDark ? Colors.white : Colors.black87,
backgroundColor: colors.surfaceContainerHigh,
foregroundColor: colors.onSurface,
disabledBackgroundColor: disabledBgColor,
disabledForegroundColor: disabledFgColor,
minimumSize: const Size(0, 48),
@ -172,8 +166,8 @@ class _MyButtonState extends State<MyButton> {
);
case MyButtonVariant.secondaryTiny:
buttonStyle = FilledButton.styleFrom(
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
foregroundColor: isDark ? Colors.white : Colors.black87,
backgroundColor: colors.surfaceContainerHigh,
foregroundColor: colors.onSurface,
disabledBackgroundColor: disabledBgColor,
disabledForegroundColor: disabledFgColor,
minimumSize: const Size(40, 23),

View file

@ -22,17 +22,9 @@ class MyChip extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDark = isDarkMode(context);
final background = selected
? context.color.primary
: isDark
? Colors.grey[800]!
: Colors.grey[200]!;
final foreground = selected
? Colors.black87
: isDark
? Colors.white
: Colors.black87;
final colors = context.color;
final background = selected ? colors.primary : colors.surfaceContainerHigh;
final foreground = selected ? colors.onPrimary : colors.onSurface;
Widget chip = AnimatedContainer(
duration: const Duration(milliseconds: 150),
height: 36,

View file

@ -29,23 +29,19 @@ class _MyIconButtonState extends State<MyIconButton> {
@override
Widget build(BuildContext context) {
final isEnabled = widget.onPressed != null || widget.onLongPress != null;
final isDark = isDarkMode(context);
final disabledBgColor = isDark
? const Color(0xFF353535)
: const Color(0xFFE0E0E0);
final disabledFgColor = isDark
? const Color(0xFF757575)
: const Color(0xFF9E9E9E);
final colors = context.color;
final disabledBgColor = colors.surfaceContainerHighest;
final disabledFgColor = colors.onSurface.withValues(alpha: 0.38);
late final Color bgColor;
late final Color fgColor;
if (widget.variant == MyIconButtonVariant.primary) {
bgColor = context.color.primary;
fgColor = Colors.black87;
fgColor = colors.onPrimary;
} else {
bgColor = isDark ? Colors.grey[800]! : Colors.grey[200]!;
fgColor = isDark ? Colors.white : Colors.black87;
bgColor = colors.surfaceContainerHigh;
fgColor = colors.onSurface;
}
final childButton = FilledButton(

View file

@ -45,23 +45,11 @@ class MyInput extends StatefulWidget {
class _MyInputState extends State<MyInput> {
@override
Widget build(BuildContext context) {
final isDark = isDarkMode(context);
final inputFillColor = isDark
? Colors.white.withValues(alpha: 0.08)
: Colors.black.withValues(alpha: 0.05);
final inputBorderColor = isDark
? Colors.white.withValues(alpha: 0.05)
: Colors.black.withValues(alpha: 0.05);
final inputHintColor = isDark
? Colors.white.withValues(alpha: 0.5)
: Colors.black.withValues(alpha: 0.5);
final prefixIconColor = isDark
? Colors.white.withValues(alpha: 0.6)
: Colors.black.withValues(alpha: 0.6);
final colors = context.color;
final inputFillColor = colors.surfaceContainerLow;
final inputBorderColor = colors.outlineVariant.withValues(alpha: 0.6);
final inputHintColor = colors.onSurfaceVariant.withValues(alpha: 0.75);
final prefixIconColor = colors.onSurfaceVariant;
return ReactiveTapFeedback(
behavior: HitTestBehavior.translucent,
@ -109,7 +97,7 @@ class _MyInputState extends State<MyInput> {
style: TextStyle(
fontSize: widget.dense ? 16 : 18,
fontWeight: widget.fontWeight ?? FontWeight.w500,
color: isDark ? Colors.white : Colors.black87,
color: colors.onSurface,
),
decoration: InputDecoration(
isDense: widget.dense,
@ -139,25 +127,25 @@ class _MyInputState extends State<MyInput> {
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(widget.dense ? 12 : 18),
borderSide: BorderSide(
color: isDark ? Colors.white : Colors.black87,
color: colors.onSurface,
width: 2,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(widget.dense ? 12 : 18),
borderSide: const BorderSide(
color: Colors.redAccent,
borderSide: BorderSide(
color: colors.error,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(widget.dense ? 12 : 18),
borderSide: const BorderSide(
color: Colors.redAccent,
borderSide: BorderSide(
color: colors.error,
width: 2,
),
),
errorStyle: const TextStyle(
color: Colors.redAccent,
errorStyle: TextStyle(
color: colors.error,
fontSize: 14,
fontWeight: FontWeight.w500,
),
@ -173,7 +161,7 @@ class _MyInputState extends State<MyInput> {
suffixIcon: widget.suffixIcon != null
? IconTheme(
data: IconThemeData(
color: isDark ? Colors.white : Colors.black87,
color: colors.onSurface,
),
child: widget.suffixIcon!,
)

View file

@ -10,23 +10,23 @@ class ProBadge extends StatelessWidget {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.amber.shade700,
color: context.appColor(AppColor.premium),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const FaIcon(
FaIcon(
FontAwesomeIcons.star,
size: 10,
color: Colors.white,
color: context.appColor(AppColor.onWarning),
),
const SizedBox(width: 4),
Text(
context.lang.backupCloudProBadge,
style: const TextStyle(
style: TextStyle(
fontSize: 10,
color: Colors.white,
color: context.appColor(AppColor.onWarning),
fontWeight: FontWeight.bold,
),
),

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:video_player/video_player.dart';
class VideoPlayerHelper extends StatefulWidget {
@ -98,15 +99,15 @@ class _VideoPlayerHelperState extends State<VideoPlayerHelper>
child: Container(
width: 64,
height: 64,
decoration: const BoxDecoration(
color: Colors.black54,
decoration: BoxDecoration(
color: context.appColor(AppColor.mediaScrim),
shape: BoxShape.circle,
),
child: Icon(
_isPaused
? Icons.pause_rounded
: Icons.play_arrow_rounded,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 36,
),
),

View file

@ -1,41 +1,97 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
class DefaultColors {
static const messageSelf = Color.fromARGB(255, 58, 136, 102);
static const messageOther = Color.fromARGB(233, 68, 137, 255);
/// Every Twonly-specific color, with its light and dark values side by side.
///
/// To add a color, add exactly one entry here and use it with:
/// `context.appColor(AppColor.yourColor)`.
enum AppColor {
// Chat bubbles
chatBubbleSent(light: Color(0xFFE8EBE9), dark: Color(0xFF292C2A)),
chatBubbleReceived(light: Color(0xFFF3F4F3), dark: Color(0xFF1D201E)),
onChatBubbleSent(light: Color(0xFF191C1A), dark: Color(0xFFF0F2F0)),
onChatBubbleReceived(light: Color(0xFF191C1A), dark: Color(0xFFE2E4E1)),
chatBubbleDeleted(light: Color(0xFFE1E4E1), dark: Color(0xFF101311)),
onChatBubbleDeleted(light: Color(0xFF626863), dark: Color(0xFF8A918B)),
chatMessageMeta(light: Color(0xFF69706A), dark: Color(0xFFAAB2AC)),
reactionBackground(light: Color(0xFFE1E5E2), dark: Color(0xFF343936)),
onReaction(light: Color(0xFF191C1A), dark: Color(0xFFF0F2F0)),
// Feedback and status
success(light: Color(0xFF177245), dark: Color(0xFF65D99A)),
onSuccess(light: Color(0xFFFFFFFF), dark: Color(0xFF00391F)),
successContainer(light: Color(0xFFC0F0D2), dark: Color(0xFF00522F)),
onSuccessContainer(light: Color(0xFF002112), dark: Color(0xFF83F7B5)),
warning(light: Color(0xFF8A5700), dark: Color(0xFFFFB95F)),
onWarning(light: Color(0xFFFFFFFF), dark: Color(0xFF492900)),
warningContainer(light: Color(0xFFFFDDB4), dark: Color(0xFF673D00)),
onWarningContainer(light: Color(0xFF2C1700), dark: Color(0xFFFFDDB4)),
info(light: Color(0xFF35618D), dark: Color(0xFF9FCBFF)),
onInfo(light: Color(0xFFFFFFFF), dark: Color(0xFF003257)),
infoContainer(light: Color(0xFFD1E4FF), dark: Color(0xFF164A70)),
onInfoContainer(light: Color(0xFF001D35), dark: Color(0xFFD1E4FF)),
premium(light: Color(0xFF8A6500), dark: Color(0xFFFFC94C)),
verificationPending(light: Color(0xFF007FA3), dark: Color(0xFF5DD5F5)),
verificationIdle(light: Color(0xFF7A8497), dark: Color(0xFFAEB8CC)),
// QR codes
qrForeground(light: Color(0xFF000000), dark: Color(0xFF000000)),
qrBackground(light: Color(0xFFFFFFFF), dark: Color(0xFFFFFFFF)),
// Controls displayed over photos and videos
mediaForeground(light: Color(0xFFFFFFFF), dark: Color(0xFFFFFFFF)),
mediaForegroundMuted(light: Color(0xB3FFFFFF), dark: Color(0xB3FFFFFF)),
mediaBackground(light: Color(0xFF000000), dark: Color(0xFF000000)),
mediaScrim(light: Color(0x8A000000), dark: Color(0x8A000000)),
recording(light: Color(0xFFD32F2F), dark: Color(0xFFFF5252)),
scanHighlight(light: Color(0xFF76FF03), dark: Color(0xFF76FF03)),
// Message-kind indicators
messageText(light: Color(0xFF35618D), dark: Color(0xFF9FCBFF)),
messageRestore(light: Color(0xFFB85F00), dark: Color(0xFFFFB95F)),
messageWidget(light: Color(0xFF79528D), dark: Color(0xFFD7B9E8)),
messageVideo(light: Color(0xFF9C3A8D), dark: Color(0xFFFFA9E5)),
messageAudio(light: Color(0xFFB85F00), dark: Color(0xFFFFB68A)),
messageImage(light: Color(0xFFBA1A1A), dark: Color(0xFFFFB4AB));
const AppColor({required this.light, required this.dark});
final Color light;
final Color dark;
Color resolve(Brightness brightness) {
return brightness == Brightness.dark ? dark : light;
}
}
Color getMessageColorFromType(
Message message,
MediaFile? mediaFile,
BuildContext context,
) {
Color color;
/// Resolves [AppColor] tokens for one theme and keeps theme transitions smooth.
@immutable
class AppColors extends ThemeExtension<AppColors> {
const AppColors.light()
: _brightness = Brightness.light,
_interpolatedColors = null;
if (message.type == MessageType.restoreFlameCounter.name) {
color = Colors.orange;
} else if (message.type == MessageType.text.name) {
color = Colors.blueAccent;
} else if (message.isWidgetMedia) {
color = const Color.fromARGB(255, 155, 89, 182);
} else if (mediaFile != null) {
if (mediaFile.requiresAuthentication) {
color = context.color.primary;
} else {
if (mediaFile.type == MediaType.video) {
color = const Color.fromARGB(255, 243, 33, 208);
} else if (mediaFile.type == MediaType.audio) {
color = const Color.fromARGB(255, 252, 149, 85);
} else {
color = Colors.redAccent;
const AppColors.dark()
: _brightness = Brightness.dark,
_interpolatedColors = null;
const AppColors._interpolated(this._interpolatedColors) : _brightness = null;
final Brightness? _brightness;
final Map<AppColor, Color>? _interpolatedColors;
Color operator [](AppColor color) {
return _interpolatedColors?[color] ?? color.resolve(_brightness!);
}
@override
AppColors copyWith() => this;
@override
AppColors lerp(covariant AppColors? other, double t) {
if (other == null) return this;
return AppColors._interpolated({
for (final color in AppColor.values)
color: Color.lerp(this[color], other[color], t)!,
});
}
}
} else {
return (isDarkMode(context)) ? Colors.white : Colors.black;
}
return color;
}

View file

@ -1,21 +1,55 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:twonly/src/visual/themes/colors.dart';
import 'package:twonly/src/visual/themes/light.dart';
ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) {
final base = ThemeData.dark().copyWith(
colorScheme: ColorScheme.fromSeed(
Color _onPrimary(Color primary) {
return ThemeData.estimateBrightnessForColor(primary) == Brightness.dark
? const Color(0xFFFFFFFF)
: const Color(0xFF082117);
}
ColorScheme _darkColorScheme(Color primary) {
return ColorScheme.fromSeed(
brightness: Brightness.dark,
seedColor: primary,
primary: primary,
surface: const Color.fromARGB(255, 20, 18, 23),
surfaceContainer: const Color.fromARGB(255, 45, 41, 54),
surfaceContainerLow: const Color.fromARGB(255, 38, 34, 45),
surfaceContainerHigh: const Color.fromARGB(255, 52, 48, 62),
),
).copyWith(
onPrimary: _onPrimary(primary),
surface: const Color(0xFF111412),
onSurface: const Color(0xFFE2E4E1),
onSurfaceVariant: const Color(0xFFBEC9C0),
surfaceDim: const Color(0xFF111412),
surfaceBright: const Color(0xFF373A37),
surfaceContainerLowest: const Color(0xFF0B0E0C),
surfaceContainerLow: const Color(0xFF171A18),
surfaceContainer: const Color(0xFF1D211E),
surfaceContainerHigh: const Color(0xFF282C29),
surfaceContainerHighest: const Color(0xFF333734),
outline: const Color(0xFF89938B),
outlineVariant: const Color(0xFF404842),
inverseSurface: const Color(0xFFE2E4E1),
onInverseSurface: const Color(0xFF2E312F),
shadow: const Color(0xFF000000),
scrim: const Color(0xFF000000),
error: const Color(0xFFFFB4AB),
onError: const Color(0xFF690005),
errorContainer: const Color(0xFF93000A),
onErrorContainer: const Color(0xFFFFDAD6),
);
}
ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) {
final colorScheme = _darkColorScheme(primary);
final base = ThemeData(
brightness: Brightness.dark,
colorScheme: colorScheme,
scaffoldBackgroundColor: colorScheme.surface,
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
),
extensions: const [AppColors.dark()],
);
return base.copyWith(
textTheme: base.textTheme.apply(

View file

@ -1,20 +1,56 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/themes/colors.dart';
const defaultPrimaryColor = Color(0xFF32be80);
const defaultPrimaryColor = Color(0xFF32BE80);
ThemeData getLightTheme([Color primary = defaultPrimaryColor]) {
final base = ThemeData(
colorScheme: ColorScheme.fromSeed(
Color _onPrimary(Color primary) {
return ThemeData.estimateBrightnessForColor(primary) == Brightness.dark
? const Color(0xFFFFFFFF)
: const Color(0xFF082117);
}
/// Material colors for the light theme. Twonly-specific light/dark pairs live
/// together in `colors.dart`.
ColorScheme _lightColorScheme(Color primary) {
return ColorScheme.fromSeed(
seedColor: primary,
primary: primary,
surface: Colors.white,
),
scaffoldBackgroundColor: Colors.white,
).copyWith(
onPrimary: _onPrimary(primary),
surface: const Color(0xFFFFFFFF),
onSurface: const Color(0xFF191C1A),
onSurfaceVariant: const Color(0xFF414943),
surfaceDim: const Color(0xFFD9DCD9),
surfaceBright: const Color(0xFFFFFFFF),
surfaceContainerLowest: const Color(0xFFFFFFFF),
surfaceContainerLow: const Color(0xFFF5F7F5),
surfaceContainer: const Color(0xFFEFF2EF),
surfaceContainerHigh: const Color(0xFFE9ECE9),
surfaceContainerHighest: const Color(0xFFE3E7E3),
outline: const Color(0xFF717971),
outlineVariant: const Color(0xFFC1C9C1),
inverseSurface: const Color(0xFF2E312F),
onInverseSurface: const Color(0xFFF0F2F0),
shadow: const Color(0xFF000000),
scrim: const Color(0xFF000000),
error: const Color(0xFFBA1A1A),
onError: const Color(0xFFFFFFFF),
errorContainer: const Color(0xFFFFDAD6),
onErrorContainer: const Color(0xFF410002),
);
}
ThemeData getLightTheme([Color primary = defaultPrimaryColor]) {
final colorScheme = _lightColorScheme(primary);
final base = ThemeData(
colorScheme: colorScheme,
scaffoldBackgroundColor: colorScheme.surface,
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
),
extensions: const [AppColors.light()],
);
return base.copyWith(
textTheme: base.textTheme.apply(
@ -28,20 +64,17 @@ final ThemeData lightTheme = getLightTheme();
final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom(
backgroundColor: defaultPrimaryColor,
foregroundColor: Colors.black87,
foregroundColor: const Color(0xFF082117),
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
);
ButtonStyle secondaryGreyButtonStyle(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return FilledButton.styleFrom(
backgroundColor: isDarkMode(context) ? Colors.grey[800] : Colors.grey[200],
foregroundColor: isDarkMode(context) ? Colors.white : Colors.black87,
backgroundColor: colors.surfaceContainerHigh,
foregroundColor: colors.onSurface,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
);
}

View file

@ -1,5 +1,6 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart';
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
@ -96,7 +97,9 @@ class MainCameraPreview extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white.withAlpha(150),
color: context.appColor(
AppColor.mediaForegroundMuted,
),
),
),
),

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/zoom_tutorial_overlay.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
@ -66,13 +67,13 @@ class CameraBottomControls extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (!isVideoRecording)
_buildSideButtonLeft()
_buildSideButtonLeft(context)
else
_buildLockOrStopButton(),
_buildShutterButton(),
_buildLockOrStopButton(context),
_buildShutterButton(context),
if (!isVideoRecording)
if (isFront)
_buildSideButtonRight()
_buildSideButtonRight(context)
else
const SizedBox(width: 80)
else
@ -85,7 +86,7 @@ class CameraBottomControls extends StatelessWidget {
);
}
Widget _buildLockOrStopButton() {
Widget _buildLockOrStopButton(BuildContext context) {
if (videoRecordingLocked) {
// Show stop button
return GestureDetector(
@ -100,7 +101,7 @@ class CameraBottomControls extends StatelessWidget {
width: 34,
height: 34,
decoration: BoxDecoration(
color: Colors.red,
color: context.appColor(AppColor.recording),
borderRadius: BorderRadius.circular(6),
),
),
@ -113,7 +114,7 @@ class CameraBottomControls extends StatelessWidget {
}
}
Widget _buildSideButtonLeft() {
Widget _buildSideButtonLeft(BuildContext context) {
return GestureDetector(
onTap: onPressSideButtonLeft,
child: Align(
@ -128,7 +129,7 @@ class CameraBottomControls extends StatelessWidget {
? FontAwesomeIcons.xmark
: FontAwesomeIcons.arrowLeft
: FontAwesomeIcons.photoFilm,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 25,
),
),
@ -137,7 +138,7 @@ class CameraBottomControls extends StatelessWidget {
);
}
Widget _buildShutterButton() {
Widget _buildShutterButton(BuildContext context) {
return StreamBuilder(
stream: userService.onUserUpdated,
builder: (context, snapshot) {
@ -156,7 +157,9 @@ class CameraBottomControls extends StatelessWidget {
shape: BoxShape.circle,
border: Border.all(
width: 7,
color: isVideoRecording ? Colors.red : Colors.white,
color: isVideoRecording
? context.appColor(AppColor.recording)
: context.appColor(AppColor.mediaForeground),
),
),
child: mc.currentFilterType.preview,
@ -168,7 +171,7 @@ class CameraBottomControls extends StatelessWidget {
);
}
Widget _buildSideButtonRight() {
Widget _buildSideButtonRight(BuildContext context) {
return GestureDetector(
onTap: onPressSideButtonRight,
child: Align(
@ -184,7 +187,7 @@ class CameraBottomControls extends StatelessWidget {
? FontAwesomeIcons.xmark
: FontAwesomeIcons.arrowRight
: FontAwesomeIcons.faceGrinTongueSquint,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 25,
),
),
@ -249,10 +252,10 @@ class _AnimatedLockButtonState extends State<_AnimatedLockButton>
height: 50,
width: 80,
padding: const EdgeInsets.all(2),
child: const Center(
child: Center(
child: FaIcon(
FontAwesomeIcons.lock,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 25,
),
),

View file

@ -59,7 +59,7 @@ class CameraScannedOverlay extends StatelessWidget {
color: Colors.transparent,
child: FaIcon(
FontAwesomeIcons.shareFromSquare,
color: isDarkMode(context) ? Colors.white : Colors.black,
color: context.color.onSurface,
size: 17,
),
),

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
/// Full-screen white overlay used as a "flash" when taking selfies
/// with the front camera and flash enabled.
@ -11,7 +12,7 @@ class CameraSelfieFlash extends StatelessWidget {
child: ClipRRect(
borderRadius: BorderRadius.circular(22),
child: Container(
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
),
),
);

View file

@ -42,14 +42,14 @@ class CameraTopActions extends StatelessWidget {
: Icons.flash_off_rounded,
tooltipText: context.lang.toggleFlashLight,
color: selectedCameraDetails.isFlashOn
? Colors.white
: Colors.white.withAlpha(160),
? context.appColor(AppColor.mediaForeground)
: context.appColor(AppColor.mediaForegroundMuted),
onPressed: onToggleFlash,
),
if (!hasAudioPermission)
ActionButton(
Icons.mic_off_rounded,
color: Colors.white.withAlpha(160),
color: context.appColor(AppColor.mediaForegroundMuted),
tooltipText: 'Allow microphone access for video recording.',
onPressed: onRequestMicrophone,
),

View file

@ -105,13 +105,13 @@ class _ZoomTutorialOverlayState extends State<ZoomTutorialOverlay>
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.black,
color: context.appColor(AppColor.mediaBackground),
borderRadius: BorderRadius.circular(16),
),
child: Text(
context.lang.dragToZoom,
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.appColor(AppColor.mediaForeground),
fontSize: 12,
fontWeight: FontWeight.w700,
letterSpacing: 0.3,
@ -131,13 +131,15 @@ class _ZoomTutorialOverlayState extends State<ZoomTutorialOverlay>
height: 42,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.5),
color: context.appColor(
AppColor.mediaForegroundMuted,
),
child: const Center(
),
child: Center(
child: FaIcon(
FontAwesomeIcons.handPointer,
size: 18,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
),
),
),

View file

@ -84,6 +84,9 @@ class _CameraPreviewControllerViewState
@override
Widget build(BuildContext context) {
widget.mainController.scanHighlightColor = context.appColor(
AppColor.scanHighlight,
);
if (AppState.hasCameraPermissions) {
return CameraPreviewView(
sendToGroup: widget.sendToGroup,

View file

@ -104,6 +104,7 @@ class MainCameraController {
bool _isBusy = false;
bool _isBusyFaces = false;
CustomPaint? qrCodePain;
Color? scanHighlightColor;
CustomPaint? facePaint;
Offset? focusPointOffset;
@ -529,12 +530,14 @@ class MainCameraController {
final controller = cameraController;
if (inputImage.metadata?.size != null &&
inputImage.metadata?.rotation != null &&
controller != null) {
controller != null &&
scanHighlightColor != null) {
final painter = BarcodeDetectorPainter(
barcodes,
inputImage.metadata!.size,
inputImage.metadata!.rotation,
controller.description.lensDirection,
scanHighlightColor!,
);
qrCodePain = CustomPaint(painter: painter);

View file

@ -10,19 +10,21 @@ class BarcodeDetectorPainter extends CustomPainter {
this.imageSize,
this.rotation,
this.cameraLensDirection,
this.color,
);
final List<Barcode> barcodes;
final Size imageSize;
final InputImageRotation rotation;
final CameraLensDirection cameraLensDirection;
final Color color;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3.0
..color = Colors.lightGreenAccent;
..color = color;
for (final barcode in barcodes) {
final cornerPoints = <Offset>[];

View file

@ -1,6 +1,7 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
import 'package:twonly/src/utils/misc.dart';
abstract class FaceFilterPainter extends CustomPainter {
FaceFilterPainter(
@ -34,7 +35,7 @@ class Preview extends StatelessWidget {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.withValues(alpha: 0.2),
color: context.color.surfaceContainerHighest,
),
child: Center(
child: child,

View file

@ -89,12 +89,14 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
mainAxisSize: MainAxisSize.min,
children: [
if (_imageSaving || widget.isLoading)
const SizedBox(
SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator.adaptive(
strokeWidth: 1,
valueColor: AlwaysStoppedAnimation(Colors.white),
valueColor: AlwaysStoppedAnimation(
context.appColor(AppColor.mediaForeground),
),
),
)
else if (_imageSaved)

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/utils/misc.dart';
class ShowTitleText extends StatelessWidget {
const ShowTitleText({
@ -15,13 +16,13 @@ class ShowTitleText extends StatelessWidget {
@override
Widget build(BuildContext context) {
final textStyle = TextStyle(
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
fontWeight: FontWeight.bold,
fontSize: isLink ? 14 : 24,
decoration: TextDecoration.none,
shadows: const [
shadows: [
Shadow(
color: Color.fromARGB(122, 0, 0, 0),
color: context.appColor(AppColor.mediaScrim),
blurRadius: 5,
),
],

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
class VideoRecordingTimer extends StatelessWidget {
const VideoRecordingTimer({
@ -38,19 +39,23 @@ class VideoRecordingTimer extends StatelessWidget {
maxRecordingTime.inMilliseconds)
.clamp(0.0, 1.0),
strokeWidth: 4,
valueColor: const AlwaysStoppedAnimation<Color>(Colors.red),
backgroundColor: Colors.grey[300],
valueColor: AlwaysStoppedAnimation<Color>(
context.appColor(AppColor.recording),
),
backgroundColor: context.appColor(
AppColor.mediaForegroundMuted,
),
),
),
Center(
child: Text(
elapsed.inSeconds.toString(),
textAlign: TextAlign.center,
style: const TextStyle(
style: TextStyle(
fontSize: 17,
shadows: [
Shadow(
color: Color.fromARGB(122, 0, 0, 0),
color: context.appColor(AppColor.mediaScrim),
blurRadius: 5,
),
],

View file

@ -6,6 +6,7 @@ import 'dart:math';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
String beautifulZoomScale(double scale) {
@ -60,7 +61,7 @@ class CameraZoomButtons extends StatelessWidget {
final zoomButtonStyle = TextButton.styleFrom(
padding: EdgeInsets.zero,
foregroundColor: Colors.white,
foregroundColor: context.appColor(AppColor.mediaForeground),
minimumSize: const Size(40, 40),
alignment: Alignment.center,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
@ -90,7 +91,7 @@ class CameraZoomButtons extends StatelessWidget {
child: ClipRRect(
borderRadius: BorderRadius.circular(40),
child: ColoredBox(
color: const Color.fromARGB(90, 0, 0, 0),
color: context.appColor(AppColor.mediaScrim),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -98,7 +99,9 @@ class CameraZoomButtons extends StatelessWidget {
TextButton(
style: zoomButtonStyle.copyWith(
foregroundColor: WidgetStateProperty.all(
isSmallerFocused ? Colors.yellow : Colors.white,
isSmallerFocused
? context.appColor(AppColor.warning)
: context.appColor(AppColor.mediaForeground),
),
),
onPressed: () async {
@ -121,7 +124,9 @@ class CameraZoomButtons extends StatelessWidget {
TextButton(
style: zoomButtonStyle.copyWith(
foregroundColor: WidgetStateProperty.all(
isMiddleFocused ? Colors.yellow : Colors.white,
isMiddleFocused
? context.appColor(AppColor.warning)
: context.appColor(AppColor.mediaForeground),
),
),
onPressed: () async {
@ -142,7 +147,9 @@ class CameraZoomButtons extends StatelessWidget {
TextButton(
style: zoomButtonStyle.copyWith(
foregroundColor: WidgetStateProperty.all(
(scaleFactor >= 2) ? Colors.yellow : Colors.white,
(scaleFactor >= 2)
? context.appColor(AppColor.warning)
: context.appColor(AppColor.mediaForeground),
),
),
onPressed: () async {

View file

@ -87,7 +87,7 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.7),
color: context.appColor(AppColor.mediaScrim),
borderRadius: BorderRadius.circular(12),
),
child: Text(
@ -96,8 +96,8 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
getContactDisplayName(widget.contact!),
)
: context.lang.qrScannerVerifyHint,
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.appColor(AppColor.mediaForeground),
fontSize: 14,
),
textAlign: TextAlign.center,

View file

@ -429,13 +429,13 @@ class _ShareImageView extends State<ShareImageView> {
mainAxisSize: MainAxisSize.min,
children: [
if (!mediaStoreFutureReady || sendingImage)
const SizedBox(
SizedBox(
height: 14,
width: 14,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(
Colors.black87,
context.color.onPrimary,
),
),
)

View file

@ -49,10 +49,10 @@ class BestFriendsSelector extends StatelessWidget {
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline.withAlpha(50),
boxShadow: const [
boxShadow: [
BoxShadow(
blurRadius: 10.9,
color: Color.fromRGBO(0, 0, 0, 0.1),
color: context.color.shadow.withValues(alpha: 0.1),
),
],
borderRadius: BorderRadius.circular(8),
@ -135,10 +135,10 @@ class UserCheckbox extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline.withAlpha(50),
boxShadow: const [
boxShadow: [
BoxShadow(
blurRadius: 10.9,
color: Color.fromRGBO(0, 0, 0, 0.1),
color: context.color.shadow.withValues(alpha: 0.1),
),
],
borderRadius: BorderRadius.circular(8),

View file

@ -28,7 +28,7 @@ class _SelectShowTimeState extends State<SelectShowTime> {
height: 350,
padding: const EdgeInsets.only(top: 6),
margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
color: CupertinoColors.systemBackground.resolveFrom(context),
color: Theme.of(context).colorScheme.surface,
child: SafeArea(
top: false,
child: Column(
@ -37,7 +37,7 @@ class _SelectShowTimeState extends State<SelectShowTime> {
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.grey,
color: Theme.of(context).colorScheme.outline,
),
height: 3,
width: 60,

View file

@ -545,9 +545,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
await askToCloseThenClose();
},
child: Scaffold(
backgroundColor: widget.sharedFromGallery
? null
: Colors.white.withAlpha(0),
backgroundColor: widget.sharedFromGallery ? null : Colors.transparent,
resizeToAvoidBottomInset: false,
body: Stack(
key: _editorStackKey,

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/misc.dart';
class ActionButton extends StatelessWidget {
const ActionButton(
@ -27,11 +28,11 @@ class ActionButton extends StatelessWidget {
icon as FaIconData?,
size: 25,
color: disable
? const Color.fromARGB(154, 255, 255, 255)
: color ?? Colors.white,
shadows: const [
? context.appColor(AppColor.mediaForegroundMuted)
: color ?? context.appColor(AppColor.mediaForeground),
shadows: [
Shadow(
color: Color.fromARGB(122, 0, 0, 0),
color: context.appColor(AppColor.mediaScrim),
blurRadius: 5,
),
],
@ -40,11 +41,11 @@ class ActionButton extends StatelessWidget {
icon as IconData?,
size: 30,
color: disable
? const Color.fromARGB(154, 255, 255, 255)
: color ?? Colors.white,
shadows: const [
? context.appColor(AppColor.mediaForegroundMuted)
: color ?? context.appColor(AppColor.mediaForeground),
shadows: [
Shadow(
color: Color.fromARGB(122, 0, 0, 0),
color: context.appColor(AppColor.mediaScrim),
blurRadius: 5,
),
],

View file

@ -53,7 +53,7 @@ Future<void> showDisplayTimePicker(
await showModalBottomSheet<void>(
context: context,
backgroundColor: Colors.black,
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (sheetContext) {
return SelectShowTime(
initialItem: initialItem,

View file

@ -65,12 +65,14 @@ class EditorBottomBar extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
if (isSending)
const SizedBox(
SizedBox(
height: 12,
width: 12,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.black87),
valueColor: AlwaysStoppedAnimation(
context.color.onPrimary,
),
),
)
else

View file

@ -107,7 +107,7 @@ class EditorSideToolbar extends StatelessWidget {
onPressed: () async {
final layer = await showModalBottomSheet<Layer>(
context: context,
backgroundColor: Colors.black,
backgroundColor: context.color.surface,
builder: (context) => const EmojiPickerBottom(),
);
if (layer == null) return;
@ -124,7 +124,7 @@ class EditorSideToolbar extends StatelessWidget {
tooltipText: context.lang.shareImageSendToWidget,
color: sendToWidget || highlightWidgetOption
? Theme.of(context).colorScheme.primary
: Colors.white,
: context.appColor(AppColor.mediaForeground),
disable: isUpdatingWidgetMode,
onPressed: onToggleSendToWidget,
),
@ -148,7 +148,7 @@ class EditorSideToolbar extends StatelessWidget {
tooltipText: 'Trim video',
color: trimmerVisible
? Theme.of(context).colorScheme.primary
: Colors.white,
: context.appColor(AppColor.mediaForeground),
onPressed: onToggleTrimmer,
),
],
@ -160,8 +160,8 @@ class EditorSideToolbar extends StatelessWidget {
: Icons.volume_up_rounded,
tooltipText: 'Enable Audio in Video',
color: (mediaService.removeAudio)
? Colors.white.withAlpha(160)
: Colors.white,
? context.appColor(AppColor.mediaForegroundMuted)
: context.appColor(AppColor.mediaForeground),
onPressed: onToggleAudio,
),
],
@ -170,7 +170,7 @@ class EditorSideToolbar extends StatelessWidget {
ActionButton(
Icons.crop_rotate_outlined,
tooltipText: 'Crop or rotate image',
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
onPressed: () {
layerStack.toggleBackgroundEditing();
onChanged();
@ -184,7 +184,7 @@ class EditorSideToolbar extends StatelessWidget {
tooltipText: context.lang.protectAsARealTwonly,
color: media.requiresAuthentication
? Theme.of(context).colorScheme.primary
: Colors.white,
: context.appColor(AppColor.mediaForeground),
onPressed: onToggleRequiresAuth,
),
],

View file

@ -109,8 +109,8 @@ class _DrawLayerState extends State<DrawLayer> {
FontAwesomeIcons.arrowRotateLeft,
tooltipText: context.lang.undo,
color: widget.layerData.control.paths.isNotEmpty
? Colors.white
: Colors.white.withAlpha(80),
? context.appColor(AppColor.mediaForeground)
: context.appColor(AppColor.mediaForegroundMuted),
onPressed: () {
if (widget.layerData.control.paths.isEmpty) return;
skipNextEvent = true;
@ -123,8 +123,8 @@ class _DrawLayerState extends State<DrawLayer> {
tooltipText: context.lang.redo,
FontAwesomeIcons.arrowRotateRight,
color: undoList.isNotEmpty
? Colors.white
: Colors.white.withAlpha(80),
? context.appColor(AppColor.mediaForeground)
: context.appColor(AppColor.mediaForegroundMuted),
onPressed: () {
if (undoList.isEmpty) return;
@ -226,7 +226,7 @@ class MagnifyingGlass extends StatelessWidget {
color: color,
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
width: 2,
),
),

View file

@ -6,6 +6,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
@ -171,7 +172,9 @@ class _EmojiLayerState extends State<EmojiLayer> {
child: ActionButton(
FontAwesomeIcons.trashCan,
tooltipText: '',
color: deleteLayer ? Colors.red : Colors.white,
color: deleteLayer
? context.appColor(AppColor.recording)
: context.appColor(AppColor.mediaForeground),
),
),
),

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/datetime_filter.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/image_filter.dart';
@ -45,11 +46,11 @@ class FilterText extends StatelessWidget {
this.text, {
super.key,
this.fontSize = 24,
this.color = Colors.white,
this.color,
});
final String text;
final double fontSize;
final Color color;
final Color? color;
@override
Widget build(BuildContext context) {
@ -58,10 +59,10 @@ class FilterText extends StatelessWidget {
textAlign: TextAlign.start,
style: TextStyle(
fontSize: fontSize,
color: color,
shadows: const [
color: color ?? context.appColor(AppColor.mediaForeground),
shadows: [
Shadow(
color: Color.fromARGB(122, 0, 0, 0),
color: context.appColor(AppColor.mediaScrim),
blurRadius: 5,
),
],

View file

@ -4,9 +4,9 @@ import 'package:intl/intl.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart';
class DateTimeFilter extends StatelessWidget {
const DateTimeFilter({super.key, this.color = Colors.white});
const DateTimeFilter({super.key, this.color});
final Color color;
final Color? color;
@override
Widget build(BuildContext context) {

View file

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/src/providers/image_editor.provider.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
@ -113,7 +114,7 @@ class _TextViewState extends State<TextLayer> {
right: 0,
child: Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(100),
color: context.appColor(AppColor.mediaScrim),
),
child: TextField(
controller: textController,
@ -151,8 +152,8 @@ class _TextViewState extends State<TextLayer> {
),
),
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.appColor(AppColor.mediaForeground),
fontSize: 20,
),
),
@ -219,13 +220,13 @@ class _TextViewState extends State<TextLayer> {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withAlpha(100),
color: context.appColor(AppColor.mediaScrim),
),
child: Text(
widget.layerData.text,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.appColor(AppColor.mediaForeground),
fontSize: 20,
),
),
@ -245,7 +246,9 @@ class _TextViewState extends State<TextLayer> {
child: ActionButton(
FontAwesomeIcons.trashCan,
tooltipText: '',
color: deleteLayer ? Colors.red : Colors.white,
color: deleteLayer
? context.appColor(AppColor.recording)
: context.appColor(AppColor.mediaForeground),
),
),
),

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:video_player/video_player.dart';
/// Cuts a recorded video down to the part the user wants to send.
@ -183,7 +184,7 @@ class _VideoTrimmerState extends State<VideoTrimmer> {
decoration: BoxDecoration(
// Translucent rather than opaque: the frames being cut away stay
// visible underneath, which is most of what makes a cut readable.
color: Colors.black.withAlpha(90),
color: context.appColor(AppColor.mediaScrim),
borderRadius: BorderRadius.circular(16),
),
child: Column(
@ -243,10 +244,15 @@ class _VideoTrimmerState extends State<VideoTrimmer> {
: _format(_duration),
style: TextStyle(
fontSize: 11,
color: Colors.white.withAlpha(isTrimmed ? 235 : 150),
color: isTrimmed
? context.appColor(AppColor.mediaForeground)
: context.appColor(AppColor.mediaForegroundMuted),
fontFeatures: const [FontFeature.tabularFigures()],
shadows: const [
Shadow(color: Color.fromARGB(122, 0, 0, 0), blurRadius: 4),
shadows: [
Shadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 4,
),
],
),
),
@ -287,7 +293,9 @@ class _TrimTrack extends StatelessWidget {
right: handleWidth,
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white.withAlpha(46),
color: context
.appColor(AppColor.mediaForeground)
.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(6),
),
),
@ -307,8 +315,14 @@ class _TrimTrack extends StatelessWidget {
),
),
),
_handle(left: startOffset),
_handle(left: endOffset + handleWidth),
_handle(
left: startOffset,
foregroundColor: context.appColor(AppColor.mediaForeground),
),
_handle(
left: endOffset + handleWidth,
foregroundColor: context.appColor(AppColor.mediaForeground),
),
Positioned(
left: playheadOffset + handleWidth - 1,
top: 4,
@ -316,10 +330,13 @@ class _TrimTrack extends StatelessWidget {
width: 3,
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
borderRadius: BorderRadius.circular(2),
boxShadow: const [
BoxShadow(color: Color.fromARGB(122, 0, 0, 0), blurRadius: 4),
boxShadow: [
BoxShadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 4,
),
],
),
),
@ -328,7 +345,7 @@ class _TrimTrack extends StatelessWidget {
);
}
Widget _handle({required double left}) {
Widget _handle({required double left, required Color foregroundColor}) {
return Positioned(
left: left,
top: 0,
@ -346,7 +363,7 @@ class _TrimTrack extends StatelessWidget {
width: 2,
height: 14,
decoration: BoxDecoration(
color: Colors.white.withAlpha(220),
color: foregroundColor,
borderRadius: BorderRadius.circular(1),
),
),

View file

@ -348,7 +348,6 @@ class _ChatListViewState extends State<ChatListView>
final plan = context.select<PurchasesProvider, SubscriptionPlan>(
(p) => p.plan,
);
final dark = isDarkMode(context);
return Scaffold(
appBar: AppBar(
title: Row(
@ -392,7 +391,7 @@ class _ChatListViewState extends State<ChatListView>
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: dark ? Colors.black : Colors.white,
color: context.color.onPrimary,
),
),
),
@ -421,11 +420,11 @@ class _ChatListViewState extends State<ChatListView>
),
Center(
child: NotificationBadgeComp(
backgroundColor: dark ? Colors.white : Colors.black,
textColor: dark ? Colors.black : Colors.white,
backgroundColor: context.color.inverseSurface,
textColor: context.color.onInverseSurface,
count: badgeCount.toString(),
child: IconButton(
color: badgeCount > 0 ? Colors.black : null,
color: badgeCount > 0 ? context.color.onPrimary : null,
key: searchForOtherUsers,
icon: const FaIcon(FontAwesomeIcons.userPlus, size: 18),
onPressed: () => context.push(Routes.chatsAddNewUser),
@ -555,12 +554,12 @@ class _ChatListViewState extends State<ChatListView>
FloatingActionButton(
heroTag: 'qrcode_fab',
elevation: 2,
backgroundColor: dark ? Colors.grey[800] : Colors.grey[200],
foregroundColor: dark ? Colors.white : Colors.black87,
backgroundColor: context.color.surfaceContainerHigh,
foregroundColor: context.color.onSurface,
onPressed: () => context.push(Routes.settingsPublicProfile),
child: FaIcon(
FontAwesomeIcons.qrcode,
color: dark ? Colors.white : Colors.black87,
color: context.color.onSurface,
),
),
const SizedBox(height: 12),
@ -568,11 +567,11 @@ class _ChatListViewState extends State<ChatListView>
heroTag: 'new_chat_fab',
elevation: 2,
backgroundColor: context.color.primary,
foregroundColor: Colors.black87,
foregroundColor: context.color.onPrimary,
onPressed: () => context.push(Routes.chatsStartNewChat),
child: const FaIcon(
child: FaIcon(
FontAwesomeIcons.penToSquare,
color: Colors.black87,
color: context.color.onPrimary,
),
),
],

View file

@ -23,10 +23,10 @@ class NewsIconButtonComp extends StatelessWidget {
return NotificationBadgeComp(
count: count.toString(),
backgroundColor: context.color.primary,
textColor: Colors.black87,
textColor: context.color.onPrimary,
child: IconButton(
onPressed: () => context.push(Routes.settingsHelpNews),
color: Colors.grey,
color: context.color.onSurfaceVariant,
tooltip: context.lang.settingsHelpNews,
icon: const FaIcon(FontAwesomeIcons.bullhorn, size: 19),
),

View file

@ -77,7 +77,7 @@ class _TypingIndicatorSubtitleCompState
child: Container(
padding: const EdgeInsets.symmetric(vertical: 3),
decoration: BoxDecoration(
color: getMessageColor(true),
color: getMessageColor(context, true),
borderRadius: BorderRadius.circular(12),
),
child: Transform.scale(

View file

@ -21,7 +21,6 @@ import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
import 'package:twonly/src/visual/components/contact_groups.comp.dart';
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
import 'package:twonly/src/visual/themes/colors.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/animated_new_message.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/blink.component.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_group_action.dart';
@ -819,8 +818,10 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
}
}
Color getMessageColor(bool isOther) {
return isOther ? DefaultColors.messageSelf : DefaultColors.messageOther;
Color getMessageColor(BuildContext context, bool isOther) {
return isOther
? context.appColor(AppColor.chatBubbleReceived)
: context.appColor(AppColor.chatBubbleSent);
}
class ChatItem {

View file

@ -80,10 +80,10 @@ class _AllReactionsViewState extends State<AllReactionsView> {
topRight: Radius.circular(32),
),
color: context.color.surface,
boxShadow: const [
boxShadow: [
BoxShadow(
blurRadius: 10.9,
color: Color.fromRGBO(0, 0, 0, 0.1),
color: context.color.shadow.withValues(alpha: 0.1),
),
],
),
@ -93,7 +93,7 @@ class _AllReactionsViewState extends State<AllReactionsView> {
margin: const EdgeInsets.all(30),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.grey,
color: context.color.outline,
),
height: 3,
width: 60,

View file

@ -30,10 +30,10 @@ class MessageHistoryView extends StatelessWidget {
topRight: Radius.circular(32),
),
color: context.color.surface,
boxShadow: const [
boxShadow: [
BoxShadow(
blurRadius: 10.9,
color: Color.fromRGBO(0, 0, 0, 0.1),
color: context.color.shadow.withValues(alpha: 0.1),
),
],
),
@ -43,7 +43,7 @@ class MessageHistoryView extends StatelessWidget {
margin: const EdgeInsets.all(30),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.grey,
color: context.color.outline,
),
height: 3,
width: 60,

View file

@ -85,10 +85,10 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
topRight: Radius.circular(32),
),
color: context.color.surface,
boxShadow: const [
boxShadow: [
BoxShadow(
blurRadius: 10.9,
color: Color.fromRGBO(0, 0, 0, 0.1),
color: context.color.shadow.withValues(alpha: 0.1),
),
],
),
@ -98,7 +98,7 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
margin: const EdgeInsets.only(top: 30),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.grey,
color: context.color.outline,
),
height: 3,
width: 60,
@ -145,9 +145,7 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
color: context.color.surfaceContainer,
borderRadius: BorderRadius.circular(12),
),
child: icon is IconData
? Icon(icon)
: FaIcon(icon as FaIconData?),
child: icon is IconData ? Icon(icon) : FaIcon(icon as FaIconData?),
),
const SizedBox(height: 8),
Text(label, textAlign: TextAlign.center),

View file

@ -109,7 +109,7 @@ class _WebxdcStoreViewState extends State<WebxdcStoreView> {
margin: const EdgeInsets.only(top: 12, bottom: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.grey,
color: context.color.outline,
),
height: 3,
width: 60,

View file

@ -161,13 +161,13 @@ class _ChatGroupActionState extends State<ChatGroupAction> {
child: FaIcon(
icon,
size: 10,
color: Colors.grey,
color: context.color.onSurfaceVariant,
),
),
const WidgetSpan(child: SizedBox(width: 8)),
TextSpan(
text: text,
style: const TextStyle(color: Colors.grey),
style: TextStyle(color: context.color.onSurfaceVariant),
),
],
),

View file

@ -22,7 +22,7 @@ class ReactionRow extends StatelessWidget {
// ignore: inference_failure_on_function_invocation
await showModalBottomSheet(
context: context,
backgroundColor: Colors.black,
backgroundColor: context.color.surface,
builder: (context) {
return AllReactionsView(
message: message,
@ -103,9 +103,7 @@ class ReactionRow extends StatelessWidget {
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(12),
color: isDarkMode(context)
? const Color.fromARGB(255, 74, 74, 74)
: const Color.fromARGB(255, 197, 197, 197),
color: context.appColor(AppColor.reactionBackground),
),
child: Row(
children: [
@ -119,9 +117,7 @@ class ReactionRow extends StatelessWidget {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
color: isDarkMode(context)
? Colors.white
: Colors.black,
color: context.appColor(AppColor.onReaction),
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),

View file

@ -117,6 +117,7 @@ class _ChatAudioEntryState extends State<ChatAudioEntry> {
? InChatAudioPlayer(
path: mediaService.tempPath.path,
message: message,
foregroundColor: info.textColor,
trailing: messageTime,
)
: Container())
@ -137,12 +138,14 @@ class InChatAudioPlayer extends StatefulWidget {
const InChatAudioPlayer({
required this.path,
required this.message,
required this.foregroundColor,
this.trailing,
super.key,
});
final String path;
final Message message;
final Color foregroundColor;
/// Shown below the waveform, next to the remaining playback time.
final Widget? trailing;
@ -158,11 +161,14 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
static const double _cursorHeight = 20;
static const double _playSlotWidth = 34;
static const List<double> _rates = [1, 1.5, 2, 0.5];
static const _waveStyle = PlayerWaveStyle(
PlayerWaveStyle get _waveStyle => PlayerWaveStyle(
spacing: 4,
waveThickness: 2.5,
scaleFactor: 80,
showSeekLine: false,
fixedWaveColor: widget.foregroundColor.withValues(alpha: 0.45),
liveWaveColor: widget.foregroundColor,
backgroundColor: widget.foregroundColor.withAlpha(0),
);
AudioPlayback? _playback;
@ -307,7 +313,7 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
? FontAwesomeIcons.solidCirclePause
: FontAwesomeIcons.solidCirclePlay,
size: 28,
color: Colors.white,
color: widget.foregroundColor,
),
),
),
@ -347,7 +353,7 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
child: Container(
width: _cursorWidth,
decoration: BoxDecoration(
color: Colors.white,
color: widget.foregroundColor,
borderRadius: BorderRadius.circular(2),
),
),
@ -367,8 +373,8 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
child: Text(
formatMsToMinSec(remaining),
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: widget.foregroundColor,
fontSize: 10,
),
),

View file

@ -95,12 +95,13 @@ class _ChatContactsEntryState extends State<ChatContactsEntry> {
if (i > 0)
Divider(
height: 1,
color: Colors.white.withValues(alpha: 0.2),
color: widget.info.textColor.withValues(alpha: 0.2),
),
_ContactRow(
contact: data.contacts[i],
message: widget.message,
contactsById: widget.contactsById,
foregroundColor: widget.info.textColor,
),
],
],
@ -114,11 +115,13 @@ class _ContactRow extends StatefulWidget {
const _ContactRow({
required this.contact,
required this.message,
required this.foregroundColor,
this.contactsById,
});
final SharedContact contact;
final Message message;
final Color foregroundColor;
final Map<int, Contact>? contactsById;
@override
@ -220,36 +223,36 @@ class _ContactRowState extends State<_ContactRow> {
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
child: Row(
children: [
const FaIcon(
FaIcon(
FontAwesomeIcons.user,
color: Colors.white,
color: widget.foregroundColor,
size: 16,
),
const SizedBox(width: 8),
Flexible(
child: BetterText(
text: widget.contact.displayName,
textColor: Colors.white,
textColor: widget.foregroundColor,
),
),
if (widget.message.senderId != null && !isAdded) ...[
const Spacer(),
const SizedBox(width: 8),
if (_isLoading)
const SizedBox(
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
widget.foregroundColor,
),
),
)
else
const FaIcon(
FaIcon(
FontAwesomeIcons.userPlus,
color: Colors.white,
color: widget.foregroundColor,
size: 16,
),
],

View file

@ -31,9 +31,7 @@ class ChatDateChip extends StatelessWidget {
return Center(
child: Container(
decoration: BoxDecoration(
color: isDarkMode(context)
? const Color.fromARGB(255, 38, 38, 38)
: Colors.black.withAlpha(40),
color: context.color.surfaceContainerHigh,
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
@ -43,7 +41,7 @@ class ChatDateChip extends StatelessWidget {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
color: isDarkMode(context) ? Colors.white : Colors.grey,
color: context.color.onSurfaceVariant,
),
),
),

View file

@ -60,7 +60,7 @@ class ChatFlameRestoredEntry extends StatelessWidget {
text: context.lang.chatEntryFlameRestored(
data.restoredFlameCounter.toInt(),
),
textColor: isDarkMode(context) ? Colors.black : Colors.black,
textColor: info.textColor,
),
),
],

View file

@ -14,9 +14,9 @@ import 'package:twonly/src/services/mediafiles/media_download_policy.dart'
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/elements/better_text.element.dart';
import 'package:twonly/src/visual/themes/colors.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/common.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/in_chat_media_viewer.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_color.dart';
import 'package:twonly/src/visual/views/chats/media_viewer.view.dart';
class ChatMediaEntry extends StatefulWidget {

View file

@ -15,14 +15,12 @@ class ChatUnknownEntry extends StatelessWidget {
),
padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10),
decoration: BoxDecoration(
color: isDarkMode(context) ? Colors.black : Colors.grey,
color: context.appColor(AppColor.chatBubbleDeleted),
borderRadius: BorderRadius.circular(12),
),
child: BetterText(
text: context.lang.updateTwonlyMessage,
textColor: isDarkMode(context)
? const Color.fromARGB(255, 99, 99, 99)
: Colors.black,
textColor: context.appColor(AppColor.onChatBubbleDeleted),
),
);
}

View file

@ -28,8 +28,10 @@ BubbleInfo getBubbleInfo(
) {
final info = BubbleInfo()
..text = message.content ?? ''
..textColor = Colors.white
..color = getMessageColor(message.senderId != null)
..textColor = message.senderId != null
? context.appColor(AppColor.onChatBubbleReceived)
: context.appColor(AppColor.onChatBubbleSent)
..color = getMessageColor(context, message.senderId != null)
..displayTime = !combineTextMessageWithNext(message, nextMessage)
..displayUserName = ''
..minWidth = minWidth
@ -61,7 +63,7 @@ BubbleInfo getBubbleInfo(
..expanded = false
..color = message.quotesMessageId != null
? Colors.transparent
: getMessageColor(message.senderId != null);
: getMessageColor(context, message.senderId != null);
if (message.isDeletedFromSender) {
info
..color = context.color.surfaceBright
@ -73,10 +75,8 @@ BubbleInfo getBubbleInfo(
if (message.isDeletedFromSender) {
info
..text = context.lang.messageWasDeleted
..color = isDarkMode(context) ? Colors.black : Colors.grey;
if (isDarkMode(context)) {
info.textColor = const Color.fromARGB(255, 99, 99, 99);
}
..color = context.appColor(AppColor.chatBubbleDeleted)
..textColor = context.appColor(AppColor.onChatBubbleDeleted);
}
return info;
}

View file

@ -20,7 +20,10 @@ class FriendlyMessageTime extends StatelessWidget {
@override
Widget build(BuildContext context) {
final statusIcon = _buildStatusIcon(context, Colors.grey.shade400);
final statusIcon = _buildStatusIcon(
context,
context.appColor(AppColor.chatMessageMeta),
);
return Padding(
padding: const EdgeInsets.only(left: 6),
@ -34,7 +37,7 @@ class FriendlyMessageTime extends StatelessWidget {
height: 10,
child: FaIcon(
FontAwesomeIcons.pencil,
color: color ?? Colors.white.withAlpha(150),
color: color ?? context.appColor(AppColor.chatMessageMeta),
size: 10,
),
),
@ -48,7 +51,7 @@ class FriendlyMessageTime extends StatelessWidget {
),
style: TextStyle(
fontSize: 10,
color: color ?? Colors.white.withAlpha(150),
color: color ?? context.appColor(AppColor.chatMessageMeta),
decoration: TextDecoration.none,
fontWeight: FontWeight.normal,
),

View file

@ -145,10 +145,7 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
minHeight: 39,
),
decoration: BoxDecoration(
color: widget.info.color.withValues(alpha: 0.3),
border: Border.all(
color: widget.info.color.withValues(alpha: 0.4),
),
color: widget.info.color,
borderRadius: widget.borderRadius,
),
child: Padding(
@ -166,9 +163,7 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
if (widget.info.displayTime || widget.message.modifiedAt != null)
FriendlyMessageTime(
message: widget.message,
color: isDarkMode(context)
? Colors.white.withAlpha(100)
: Colors.black.withAlpha(100),
color: context.appColor(AppColor.chatMessageMeta),
),
],
),

View file

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
Color getMessageColorFromType(
Message message,
MediaFile? mediaFile,
BuildContext context,
) {
if (message.type == MessageType.restoreFlameCounter.name) {
return context.appColor(AppColor.messageRestore);
}
if (message.type == MessageType.text.name) {
return context.appColor(AppColor.messageText);
}
if (message.isWidgetMedia) {
return context.appColor(AppColor.messageWidget);
}
if (mediaFile == null) return context.color.onSurface;
if (mediaFile.requiresAuthentication) return context.color.primary;
return switch (mediaFile.type) {
MediaType.video => context.appColor(AppColor.messageVideo),
MediaType.audio => context.appColor(AppColor.messageAudio),
_ => context.appColor(AppColor.messageImage),
};
}

View file

@ -116,7 +116,7 @@ class MessageContextMenu extends StatelessWidget {
final layer =
await showModalBottomSheet(
context: navigator.context,
backgroundColor: Colors.black,
backgroundColor: context.color.surface,
builder: (context) {
return const EmojiPickerBottom();
},

View file

@ -324,7 +324,7 @@ class _MessageInputState extends State<MessageInput>
// ignore: inference_failure_on_function_invocation
await showModalBottomSheet(
context: context,
backgroundColor: Colors.black,
backgroundColor: context.color.surface,
builder: (context) {
return ShareAdditionalView(
group: widget.group,
@ -457,10 +457,12 @@ class _MessageInputState extends State<MessageInput>
),
child: FadeTransition(
opacity: _recordingBlink,
child: const FaIcon(
child: FaIcon(
FontAwesomeIcons.microphone,
size: 20,
color: Colors.red,
color: context.appColor(
AppColor.recording,
),
),
),
),
@ -470,9 +472,7 @@ class _MessageInputState extends State<MessageInput>
_currentDuration,
),
style: TextStyle(
color: isDarkMode(context)
? Colors.white
: Colors.black,
color: context.color.onSurface,
fontSize: 12,
),
),
@ -491,8 +491,10 @@ class _MessageInputState extends State<MessageInput>
onTap: _cancelAudioRecording,
child: Text(
context.lang.voiceMessageCancel,
style: const TextStyle(
color: Colors.red,
style: TextStyle(
color: context.appColor(
AppColor.recording,
),
),
),
),
@ -591,9 +593,7 @@ class _MessageInputState extends State<MessageInput>
borderRadius: BorderRadius.circular(
90,
),
color: isDarkMode(context)
? Colors.black
: Colors.white,
color: context.color.surface,
),
child: const Center(
child: Column(
@ -622,7 +622,9 @@ class _MessageInputState extends State<MessageInput>
right: -20,
child: Container(
decoration: BoxDecoration(
color: Colors.red,
color: context.appColor(
AppColor.recording,
),
borderRadius: BorderRadius.circular(90),
),
width: 60,
@ -644,7 +646,7 @@ class _MessageInputState extends State<MessageInput>
color:
(_recordingState ==
RecordingState.recording)
? Colors.white
? context.color.onError
: null,
(_recordingState == RecordingState.none)
? FontAwesomeIcons.microphone
@ -712,11 +714,11 @@ class _MessageInputState extends State<MessageInput>
),
searchViewConfig: SearchViewConfig(
backgroundColor: context.color.surfaceContainer,
buttonIconColor: Colors.white,
buttonIconColor: context.color.onSurface,
),
categoryViewConfig: CategoryViewConfig(
backgroundColor: context.color.surfaceContainer,
dividerColor: Colors.white,
dividerColor: context.color.outlineVariant,
indicatorColor: context.color.primary,
iconColorSelected: context.color.primary,
iconColor: context.color.secondary,

View file

@ -11,9 +11,9 @@ import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/animate_icon.comp.dart';
import 'package:twonly/src/visual/themes/colors.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/friendly_message_time.comp.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/file_limit_reached.dialog.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_color.dart';
enum MessageSendState {
received,
@ -310,7 +310,11 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
widget.group!.draftMessage != null &&
widget.group!.draftMessage != '') {
icons = [
const FaIcon(FontAwesomeIcons.pen, size: 12, color: Colors.grey),
FaIcon(
FontAwesomeIcons.pen,
size: 12,
color: context.color.onSurfaceVariant,
),
];
textWidget = Text(
'${context.lang.draftMessage}: ${substringBy(widget.group!.draftMessage!, 10)}',

View file

@ -54,7 +54,7 @@ class ResponseContainer extends StatelessWidget {
maxWidth: MediaQuery.sizeOf(context).width * 0.8,
),
decoration: BoxDecoration(
color: getMessageColor(msg.senderId != null),
color: getMessageColor(context, msg.senderId != null),
borderRadius: borderRadius,
),
child: IntrinsicWidth(
@ -187,7 +187,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
@override
Widget build(BuildContext context) {
String? subtitle;
var color = const Color.fromARGB(233, 68, 137, 255);
var color = context.appColor(AppColor.messageText);
if (_message != null) {
if (_message!.type == MessageType.text.name) {
@ -238,7 +238,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
subtitle = _storyAlive ? context.lang.story : context.lang.storyExpired;
}
color = getMessageColor(_message!.senderId != null);
color = getMessageColor(context, _message!.senderId != null);
}
final hasImage =

View file

@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
import 'package:twonly/src/visual/views/chats/chat_messages.view.dart';
@ -131,7 +132,7 @@ class _TypingIndicatorState extends State<TypingIndicator> {
vertical: 6,
),
decoration: BoxDecoration(
color: getMessageColor(true),
color: getMessageColor(context, true),
borderRadius: BorderRadius.circular(12),
),
child: AnimatedTypingDots(
@ -246,8 +247,8 @@ class _AnimatedDot extends AnimatedWidget {
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Colors.white,
decoration: BoxDecoration(
color: context.appColor(AppColor.onChatBubbleReceived),
shape: BoxShape.circle,
),
),

View file

@ -870,10 +870,10 @@ class _MediaViewerViewState extends State<MediaViewerView> {
fontWeight: FontWeight.bold,
color: showSendTextMessageInput
? null
: const Color.fromARGB(255, 126, 126, 126),
shadows: const [
: context.appColor(AppColor.mediaForegroundMuted),
shadows: [
Shadow(
color: Color.fromARGB(122, 0, 0, 0),
color: context.appColor(AppColor.mediaScrim),
blurRadius: 5,
),
],

View file

@ -3,6 +3,7 @@ import 'package:photo_view/photo_view.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'
show MediaType;
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:video_player/video_player.dart';
class MediaContentRenderer extends StatelessWidget {
@ -47,10 +48,10 @@ class MediaContentRenderer extends StatelessWidget {
initialScale: PhotoViewComputedScale.contained,
minScale: PhotoViewComputedScale.contained,
errorBuilder: (context, error, stackTrace) {
return const Center(
return Center(
child: Icon(
Icons.broken_image_outlined,
color: Colors.white38,
color: context.appColor(AppColor.mediaForegroundMuted),
size: 64,
),
);

View file

@ -3,6 +3,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/daos/stories.dao.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/elements/my_icon_button.element.dart';
import 'package:twonly/src/visual/views/chats/media_viewer_components/story_viewers.bottom_sheet.dart';
@ -23,7 +24,7 @@ class OwnStoryBottomBar extends StatelessWidget {
Future<void> _showViewers(BuildContext context, MediaFile media) async {
await showModalBottomSheet<void>(
context: context,
backgroundColor: Colors.black,
backgroundColor: context.appColor(AppColor.mediaBackground),
builder: (context) => StoryViewersBottomSheet(mediaFile: media),
);
}

View file

@ -283,7 +283,10 @@ class EmojiFloatWidgetState extends State<EmojiFloatWidget>
Widget build(BuildContext context) {
return IgnorePointer(
child: CustomPaint(
painter: _ParticlePainter(List<_Particle>.from(_particles)),
painter: _ParticlePainter(
List<_Particle>.from(_particles),
context.appColor(AppColor.mediaBackground),
),
size: Size.infinite,
),
);
@ -339,8 +342,9 @@ class _Particle {
}
class _ParticlePainter extends CustomPainter {
_ParticlePainter(this.particles);
_ParticlePainter(this.particles, this.particleColor);
final List<_Particle> particles;
final Color particleColor;
static final _textPainter = TextPainter(textDirection: TextDirection.ltr);
@ -351,7 +355,7 @@ class _ParticlePainter extends CustomPainter {
text: p.emoji,
style: TextStyle(
fontSize: 24 * p.currentScale,
color: Colors.black.withValues(alpha: p.opacity),
color: particleColor.withValues(alpha: p.opacity),
fontFamily: Platform.isAndroid ? 'sans-serif' : null,
fontFamilyFallback: Platform.isAndroid
? const ['NotoColorEmoji']

View file

@ -94,10 +94,10 @@ class _StoryViewersBottomSheetState extends State<StoryViewersBottomSheet> {
topRight: Radius.circular(32),
),
color: context.color.surface,
boxShadow: const [
boxShadow: [
BoxShadow(
blurRadius: 10.9,
color: Color.fromRGBO(0, 0, 0, 0.1),
color: context.color.shadow.withValues(alpha: 0.1),
),
],
),
@ -107,7 +107,7 @@ class _StoryViewersBottomSheetState extends State<StoryViewersBottomSheet> {
margin: const EdgeInsets.only(top: 20, bottom: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32),
color: Colors.grey,
color: context.color.outline,
),
height: 3,
width: 60,

View file

@ -238,7 +238,7 @@ class _MessageInfoViewState extends State<MessageInfoView> {
// ignore: inference_failure_on_function_invocation
await showModalBottomSheet(
context: context,
backgroundColor: Colors.black,
backgroundColor: context.color.surface,
builder: (context) {
return MessageHistoryView(
message: widget.message,

View file

@ -17,7 +17,7 @@ import 'package:twonly/src/visual/components/flame_counter.comp.dart';
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
import 'package:twonly/src/visual/context_menu/group.context_menu.dart';
import 'package:twonly/src/visual/context_menu/user.context_menu.dart';
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
import 'package:twonly/src/visual/elements/my_input.element.dart';
import 'package:twonly/src/visual/views/chats/chat_messages.view.dart';
class StartNewChatView extends StatefulWidget {
@ -65,6 +65,7 @@ class _StartNewChatView extends State<StartNewChatView> {
void dispose() {
allNonDirectGroupsSub.cancel();
contactSub.cancel();
searchUserName.dispose();
super.dispose();
}
@ -142,22 +143,20 @@ class _StartNewChatView extends State<StartNewChatView> {
padding: const EdgeInsets.only(
bottom: 40,
left: 10,
top: 20,
top: 0,
right: 10,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField(
onChanged: (_) async {
await filterUsers();
},
child: MyInput(
controller: searchUserName,
decoration: getInputDecoration(
context,
context.lang.startNewChatSearchHint,
),
dense: true,
fontWeight: FontWeight.normal,
hintText: context.lang.startNewChatSearchHint,
prefixIcon: const Icon(Icons.search, size: 20),
onChanged: (_) => unawaited(filterUsers()),
),
),
const SizedBox(height: 10),
@ -171,12 +170,15 @@ class _StartNewChatView extends State<StartNewChatView> {
if (i == 0) {
return ListTile(
title: Text(context.lang.newGroup),
leading: const CircleAvatar(
leading: const SizedBox.square(
dimension: 26,
child: Center(
child: FaIcon(
FontAwesomeIcons.userGroup,
size: 13,
),
),
),
onTap: () => context.push(
Routes.groupCreateSelectMember(null),
),
@ -185,12 +187,15 @@ class _StartNewChatView extends State<StartNewChatView> {
if (i == 1) {
return ListTile(
title: Text(context.lang.startNewChatNewContact),
leading: const CircleAvatar(
leading: const SizedBox.square(
dimension: 26,
child: Center(
child: FaIcon(
FontAwesomeIcons.userPlus,
size: 13,
),
),
),
onTap: () => context.push(Routes.chatsAddNewUser),
);
}

View file

@ -52,9 +52,9 @@ class OpenRequestsListComp extends StatelessWidget {
).merge(secondaryGreyButtonStyle(context)),
child: Row(
children: [
const Icon(
Icon(
Icons.person_off_rounded,
color: Color.fromARGB(164, 244, 67, 54),
color: context.color.error,
),
Text(
context.lang.contactActionBlock,
@ -89,7 +89,7 @@ class OpenRequestsListComp extends StatelessWidget {
).merge(secondaryGreyButtonStyle(context)),
child: Row(
children: [
const Icon(Icons.check, color: Colors.green),
Icon(Icons.check, color: context.appColor(AppColor.success)),
Text(
context.lang.contactActionAccept,
style: const TextStyle(fontSize: 10),

View file

@ -21,7 +21,6 @@ import 'package:twonly/src/visual/components/verification_badge.comp.dart';
import 'package:twonly/src/visual/elements/better_list_title.element.dart';
import 'package:twonly/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.comp.dart';
import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart';
import 'package:twonly/src/visual/views/contact/contact_components/user_discovery_contact_settings.comp.dart';
import 'package:twonly/src/visual/views/contact/contact_components/verification_expansion_tile.comp.dart';
import 'package:twonly/src/visual/views/contact/select_contact_groups.view.dart';
import 'package:twonly/src/visual/views/groups/group.view.dart';
@ -301,9 +300,6 @@ class _ContactViewState extends State<ContactView> {
MutualGroupsExpansionTileComp(
contact: contact,
),
UserDiscoveryContactSettingsComp(
contact: contact,
),
const Divider(),
BetterListTile(
icon: FontAwesomeIcons.flag,
@ -331,7 +327,7 @@ class _ContactViewState extends State<ContactView> {
BetterListTile(
icon: FontAwesomeIcons.userMinus,
iconSize: 16,
color: Colors.red,
color: context.color.error,
text: context.lang.contactRemove,
onTap: () => handleUserRemoveRequest(contact),
),
@ -382,12 +378,16 @@ class _ContactViewState extends State<ContactView> {
SignalVersion.v1 => 'Signal Protocol (v1)',
SignalVersion.v2 => 'PQXDH (v2)',
}}',
style: const TextStyle(color: Colors.grey),
style: TextStyle(
color: context.color.onSurfaceVariant,
),
),
const SizedBox(height: 4),
Text(
'Can send widgets: ${contact.widgetSharingAllowed ? 'Yes' : 'No'}',
style: const TextStyle(color: Colors.grey),
style: TextStyle(
color: context.color.onSurfaceVariant,
),
),
],
),

View file

@ -1,82 +0,0 @@
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/user_discovery.service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/elements/better_list_title.element.dart';
import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart';
class UserDiscoveryContactSettingsComp extends StatelessWidget {
const UserDiscoveryContactSettingsComp({
required this.contact,
super.key,
});
final Contact contact;
@override
Widget build(BuildContext context) {
if (!userService.currentUser.isUserDiscoveryEnabled) {
return const SizedBox.shrink();
}
if (userService.currentUser.userDiscoveryRequiresManualApproval &&
contact.userDiscoveryManualApproved != true) {
return BetterListTile(
icon: FontAwesomeIcons.usersViewfinder,
text: context.lang.userDiscoverySettingsTitle,
subtitle: Text(
context.lang.contactUserDiscoveryManualApprovalPending,
style: const TextStyle(fontSize: 10),
),
trailing: TextButton(
onPressed: () async {
await twonlyDB.contactsDao.updateContact(
contact.userId,
const ContactsCompanion(
userDiscoveryManualApproved: Value(true),
),
);
},
child: Text(
context.lang.contactUserDiscoveryManualApprovalApprove,
),
),
);
}
return BetterListTile(
icon: FontAwesomeIcons.usersViewfinder,
text: context.lang.userDiscoverySettingsTitle,
onTap: () => context.navPush(const UserDiscoverySettingsView()),
subtitle:
!contact.userDiscoveryExcluded &&
contact.mediaSendCounter <
userService.currentUser.requiredSendImages
? Text(
context.lang.contactUserDiscoveryImagesLeft(
userService.currentUser.requiredSendImages -
contact.mediaSendCounter,
getContactDisplayName(contact),
),
style: const TextStyle(fontSize: 9),
)
: null,
trailing: Transform.scale(
scale: 0.8,
child: Switch.adaptive(
value: !contact.userDiscoveryExcluded,
onChanged: (a) async {
await UserDiscoveryService.changeExclusionForContact(
contact.userId,
!a,
);
},
),
),
);
}
}

View file

@ -187,7 +187,7 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
Future<void> _selectEmoji() async {
final result = await showModalBottomSheet<dynamic>(
context: context,
backgroundColor: Colors.black,
backgroundColor: context.color.surface,
builder: (context) => const EmojiPickerBottom(),
);
if (result is EmojiLayerData && mounted) {
@ -330,9 +330,7 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
color: color == noContactGroupBackgroundColor ? null : Color(color),
shape: BoxShape.circle,
border: Border.all(
color: selected
? (isDarkMode(context) ? Colors.white : Colors.black)
: Theme.of(context).colorScheme.outline,
color: selected ? context.color.onSurface : context.color.outline,
width: selected ? 3 : 1,
),
),
@ -431,10 +429,10 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
IconButton(
tooltip: context.lang.deleteContactGroup,
onPressed: _delete,
icon: const FaIcon(
icon: FaIcon(
FontAwesomeIcons.trashCan,
size: 18,
color: Colors.red,
color: context.color.error,
),
),
],

View file

@ -17,10 +17,10 @@ class CriticalErrorView extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FaIcon(
FaIcon(
FontAwesomeIcons.triangleExclamation,
size: 80,
color: Colors.redAccent,
color: context.color.error,
),
const SizedBox(height: 24),
Text(

View file

@ -322,7 +322,7 @@ class _GroupViewState extends State<GroupView> {
if (!_group!.leftGroup)
BetterListTile(
icon: FontAwesomeIcons.rightFromBracket,
color: Colors.red,
color: context.color.error,
text: context.lang.leaveGroup,
onTap: _leaveGroup,
)

View file

@ -42,7 +42,7 @@ class MemoriesCloudBackupPromoComp extends StatelessWidget {
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
color: context.color.shadow.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),

View file

@ -51,11 +51,11 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
width: 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: const [
boxShadow: [
BoxShadow(
color: Colors.black12,
color: context.color.shadow.withValues(alpha: 0.12),
blurRadius: 6,
offset: Offset(0, 2),
offset: const Offset(0, 2),
),
],
),
@ -68,11 +68,15 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return ColoredBox(
color: Colors.grey.shade800,
child: const Center(
color: context.appColor(
AppColor.mediaBackground,
),
child: Center(
child: Icon(
Icons.broken_image_outlined,
color: Colors.white30,
color: context.appColor(
AppColor.mediaForegroundMuted,
),
size: 32,
),
),
@ -86,7 +90,7 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
begin: Alignment.bottomCenter,
end: Alignment.center,
colors: [
Colors.black.withValues(alpha: 0.7),
context.appColor(AppColor.mediaScrim),
Colors.transparent,
],
),
@ -100,8 +104,10 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
child: Text(
text,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.appColor(
AppColor.mediaForeground,
),
fontWeight: FontWeight.bold,
fontSize: 14,
),

View file

@ -8,6 +8,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/selectable_thumbnail.comp.dart';
import 'package:twonly/src/visual/views/memories/components/memory_transition_painter.dart';
@ -234,11 +235,11 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
}
Log.warn(error);
return ColoredBox(
color: Colors.grey.shade200,
child: const Center(
color: context.color.surfaceContainerHigh,
child: Center(
child: FaIcon(
FontAwesomeIcons.image,
color: Colors.black26,
color: context.color.onSurfaceVariant,
),
),
);
@ -251,37 +252,43 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
)
else
ColoredBox(
color: Colors.grey.shade200,
child: const Center(
color: context.color.surfaceContainerHigh,
child: Center(
child: FaIcon(
FontAwesomeIcons.image,
color: Colors.black26,
color: context.color.onSurfaceVariant,
),
),
),
if (isVideo)
const Positioned.fill(
Positioned.fill(
child: Center(
child: FaIcon(
FontAwesomeIcons.circlePlay,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 32,
shadows: [
Shadow(color: Colors.black54, blurRadius: 6),
Shadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 6,
),
],
),
),
),
if (media.mediaFile.isFavorite)
const Positioned(
Positioned(
bottom: 6,
left: 6,
child: Icon(
Icons.favorite,
color: Colors.redAccent,
color: context.color.error,
size: 16,
shadows: [
Shadow(color: Colors.black54, blurRadius: 4),
Shadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 4,
),
],
),
),
@ -294,17 +301,17 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
switch (media.mediaFile.cloudState) {
case CloudState.none:
iconData = Icons.cloud_off_outlined;
color = Colors.white54;
color = context.appColor(AppColor.mediaForegroundMuted);
case CloudState.pending:
iconData = Icons.cloud_upload_outlined;
color = Colors.white70;
color = context.appColor(AppColor.mediaForegroundMuted);
case CloudState.uploaded:
if (hasStored) {
iconData = Icons.cloud_done_outlined;
color = Colors.white;
color = context.appColor(AppColor.mediaForeground);
} else {
iconData = Icons.cloud_outlined;
color = Colors.white;
color = context.appColor(AppColor.mediaForeground);
}
}
@ -315,8 +322,11 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
iconData,
color: color,
size: 16,
shadows: const [
Shadow(color: Colors.black54, blurRadius: 4),
shadows: [
Shadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 4,
),
],
),
);

View file

@ -85,15 +85,15 @@ class MemoriesSelectionMenuComp extends StatelessWidget {
value: 'delete',
child: Row(
children: [
const Icon(
Icon(
Icons.delete_forever_outlined,
size: 20,
color: Colors.redAccent,
color: context.color.error,
),
const SizedBox(width: 12),
Text(
context.lang.memoriesMenuDelete,
style: const TextStyle(color: Colors.redAccent),
style: TextStyle(color: context.color.error),
),
],
),

View file

@ -30,7 +30,7 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.50),
color: context.appColor(AppColor.mediaScrim),
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
@ -40,26 +40,28 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
if (showStoreButton)
_ToolbarAction(
icon: isImageSaving
? const SizedBox(
? SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.white),
valueColor: AlwaysStoppedAnimation(
context.appColor(AppColor.mediaForeground),
),
),
)
: const FaIcon(
: FaIcon(
FontAwesomeIcons.floppyDisk,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 18,
),
label: context.lang.galleryActionSave,
onTap: isImageSaving ? null : onStore,
),
_ToolbarAction(
icon: const FaIcon(
icon: FaIcon(
FontAwesomeIcons.download,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 19,
),
label: context.lang.galleryActionExport,
@ -68,7 +70,9 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
_ToolbarAction(
icon: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border,
color: isFavorite ? Colors.redAccent : Colors.white,
color: isFavorite
? context.color.error
: context.appColor(AppColor.mediaForeground),
size: 22,
),
label: isFavorite
@ -77,18 +81,18 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
onTap: onToggleFavorite,
),
_ToolbarAction(
icon: const Icon(
icon: Icon(
Icons.delete,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 22,
),
label: context.lang.galleryActionDelete,
onTap: onDelete,
),
_ToolbarAction(
icon: const FaIcon(
icon: FaIcon(
FontAwesomeIcons.shareNodes,
color: Colors.white,
color: context.appColor(AppColor.mediaForeground),
size: 19,
),
label: context.lang.galleryActionShare,
@ -135,7 +139,7 @@ class _ToolbarAction extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.9),
color: context.appColor(AppColor.mediaForegroundMuted),
fontSize: 10,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,

View file

@ -5,6 +5,7 @@ import 'package:photo_view/photo_view.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/helpers/video_player_file.helper.dart';
class SynchronizedViewerItemComp extends StatefulWidget {
@ -124,10 +125,10 @@ class _SynchronizedViewerItemCompState
hash: item.mediaService.mediaFile.blurhash!,
optimizationMode: BlurHashOptimizationMode.approximation,
)
: const Center(
: Center(
child: Icon(
Icons.broken_image_outlined,
color: Colors.white38,
color: context.appColor(AppColor.mediaForegroundMuted),
size: 64,
),
)
@ -143,10 +144,10 @@ class _SynchronizedViewerItemCompState
color: Colors.transparent,
),
errorBuilder: (context, error, stackTrace) {
return const Center(
return Center(
child: Icon(
Icons.broken_image_outlined,
color: Colors.white38,
color: context.appColor(AppColor.mediaForegroundMuted),
size: 64,
),
);

View file

@ -441,14 +441,14 @@ class MemoriesViewState extends State<MemoriesView>
Icon(
Icons.photo_library_outlined,
size: 64,
color: Colors.grey.shade400,
color: context.color.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
context.lang.memoriesEmpty,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.grey,
style: TextStyle(
color: context.color.onSurfaceVariant,
),
),
],
@ -489,7 +489,7 @@ class MemoriesViewState extends State<MemoriesView>
children: [
if (_isUsageLimitReached)
Container(
color: Colors.redAccent,
color: context.color.error,
width: double.infinity,
padding: const EdgeInsets.symmetric(
vertical: 8,
@ -497,8 +497,8 @@ class MemoriesViewState extends State<MemoriesView>
),
child: Text(
context.lang.memoriesBackupLimitReached,
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.color.onError,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
@ -656,7 +656,7 @@ class MemoriesViewState extends State<MemoriesView>
? Icons.favorite
: Icons.favorite_border,
color: _filterFavoritesOnly
? Colors.redAccent
? context.color.error
: null,
),
onPressed: () {

Some files were not shown because too many files have changed in this diff Show more