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/providers/settings.provider.dart';
import 'package:twonly/src/services/backup.service.dart'; import 'package:twonly/src/services/backup.service.dart';
import 'package:twonly/src/utils/log.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 { extension ShortCutsExtension on BuildContext {
AppLocalizations get lang => AppLocalizations.of(this)!; AppLocalizations get lang => AppLocalizations.of(this)!;
ColorScheme get color => Theme.of(this).colorScheme; ColorScheme get color => Theme.of(this).colorScheme;
Color appColor(AppColor color) {
return Theme.of(this).extension<AppColors>()![color];
}
Future<dynamic> navPush(Widget route) async { Future<dynamic> navPush(Widget route) async {
return Navigator.push( return Navigator.push(
this, this,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
enum SnackbarLevel { enum SnackbarLevel {
info, info,
@ -14,23 +15,28 @@ void showSnackbar(
String message, { String message, {
SnackbarLevel level = SnackbarLevel.error, SnackbarLevel level = SnackbarLevel.error,
}) { }) {
Color backgroundColor; final (backgroundColor, foregroundColor, iconData) = switch (level) {
IconData iconData; SnackbarLevel.info => (
context.appColor(AppColor.info),
switch (level) { context.appColor(AppColor.onInfo),
case SnackbarLevel.info: Icons.info_outline,
backgroundColor = Colors.blue.shade700; ),
iconData = Icons.info_outline; SnackbarLevel.success => (
case SnackbarLevel.success: context.appColor(AppColor.success),
backgroundColor = Colors.green.shade700; context.appColor(AppColor.onSuccess),
iconData = Icons.check_circle_outline; Icons.check_circle_outline,
case SnackbarLevel.warning: ),
backgroundColor = Colors.orange.shade800; SnackbarLevel.warning => (
iconData = Icons.warning_amber_rounded; context.appColor(AppColor.warning),
case SnackbarLevel.error: context.appColor(AppColor.onWarning),
backgroundColor = Colors.red.shade700; Icons.warning_amber_rounded,
iconData = Icons.error_outline; ),
} SnackbarLevel.error => (
context.color.error,
context.color.onError,
Icons.error_outline,
),
};
AnimationController? localAnimationController; AnimationController? localAnimationController;
@ -44,7 +50,8 @@ void showSnackbar(
child: _SnackbarWidget( child: _SnackbarWidget(
message: message, message: message,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
icon: Icon(iconData, color: Colors.white, size: 28), foregroundColor: foregroundColor,
icon: Icon(iconData, color: foregroundColor, size: 28),
onCloseClick: () { onCloseClick: () {
localAnimationController?.reverse(); localAnimationController?.reverse();
}, },
@ -101,11 +108,13 @@ class _SnackbarWidget extends StatelessWidget {
const _SnackbarWidget({ const _SnackbarWidget({
required this.message, required this.message,
required this.backgroundColor, required this.backgroundColor,
required this.foregroundColor,
required this.icon, required this.icon,
required this.onCloseClick, required this.onCloseClick,
}); });
final String message; final String message;
final Color backgroundColor; final Color backgroundColor;
final Color foregroundColor;
final Icon icon; final Icon icon;
final VoidCallback onCloseClick; final VoidCallback onCloseClick;
@ -118,9 +127,9 @@ class _SnackbarWidget extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor, color: backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(12)), borderRadius: const BorderRadius.all(Radius.circular(12)),
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black26, color: Theme.of(context).colorScheme.shadow.withValues(alpha: 0.2),
spreadRadius: 1, spreadRadius: 1,
blurRadius: 30, blurRadius: 30,
), ),
@ -138,10 +147,10 @@ class _SnackbarWidget extends StatelessWidget {
child: Text( child: Text(
message, message,
style: theme.textTheme.bodyMedium?.merge( style: theme.textTheme.bodyMedium?.merge(
const TextStyle( TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 16, fontSize: 16,
color: Colors.white, color: foregroundColor,
), ),
), ),
textAlign: TextAlign.start, textAlign: TextAlign.start,
@ -151,9 +160,13 @@ class _SnackbarWidget extends StatelessWidget {
GestureDetector( GestureDetector(
onTap: onCloseClick, onTap: onCloseClick,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: const Padding( child: Padding(
padding: EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Icon(Icons.close, color: Colors.white70, size: 20), 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/daos/key_verification.dao.dart';
import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/twonly.db.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'; import 'package:twonly/src/visual/elements/svg_icon.element.dart';
class VerificationBadgeComp extends StatefulWidget { class VerificationBadgeComp extends StatefulWidget {
@ -198,7 +198,7 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
? SvgIcons.verifiedNumeric(_verifiedByTransferredTrustCount) ? SvgIcons.verifiedNumeric(_verifiedByTransferredTrustCount)
: SvgIcons.verifiedRed, : SvgIcons.verifiedRed,
color: (_verifiedByTransferredTrustCount > 0 && !_isVerified) color: (_verifiedByTransferredTrustCount > 0 && !_isVerified)
? colorVerificationBadgeYellow ? context.appColor(AppColor.verificationPending)
: null, : null,
size: widget.size, 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/my_button.element.dart';
import 'package:twonly/src/visual/elements/svg_icon.element.dart'; import 'package:twonly/src/visual/elements/svg_icon.element.dart';
const colorVerificationBadgeYellow = Color.fromARGB(255, 0, 182, 238);
class VerificationBadgeInfo extends StatelessWidget { class VerificationBadgeInfo extends StatelessWidget {
const VerificationBadgeInfo({ const VerificationBadgeInfo({
this.displayButtons = false, this.displayButtons = false,
@ -121,19 +119,19 @@ class VerificationBadgeInfo extends StatelessWidget {
), ),
_buildItem( _buildItem(
context, context,
icon: const SvgIcon( icon: SvgIcon(
assetPath: SvgIcons.verifiedGreen, assetPath: SvgIcons.verifiedGreen,
size: 40, size: 40,
color: colorVerificationBadgeYellow, color: context.appColor(AppColor.verificationPending),
), ),
description: context.lang.verificationBadgeYellowDesc, description: context.lang.verificationBadgeYellowDesc,
boldTextColor: colorVerificationBadgeYellow, boldTextColor: context.appColor(AppColor.verificationPending),
), ),
_buildItem( _buildItem(
context, context,
icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40), icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40),
description: context.lang.verificationBadgeRedDesc, 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/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.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'; import 'package:vector_graphics/vector_graphics.dart';
/// Animated chain-link logo for the "verification success" moment. /// Animated chain-link logo for the "verification success" moment.
@ -85,9 +85,6 @@ class VerificationSuccessAnimationState
static const _svg2 = static const _svg2 =
'<svg viewBox="0 0 640 640"><path d="$_path2" fill="white"/></svg>'; '<svg viewBox="0 0 640 640"><path d="$_path2" fill="white"/></svg>';
static const _grey = Color(0xFF8E9AAF);
static const Color _green = defaultPrimaryColor;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -196,6 +193,7 @@ class VerificationSuccessAnimationState
animation: Listenable.merge([_ctrl, _idleCtrl]), animation: Listenable.merge([_ctrl, _idleCtrl]),
builder: (context, _) { builder: (context, _) {
final scale = widget.size / 640; final scale = widget.size / 640;
final successColor = context.color.primary;
final t = _flyIn.value; // 0 → 1 (fly-in progress) final t = _flyIn.value; // 0 → 1 (fly-in progress)
final sep = 1.0 - t; // 1 → 0 (separation) final sep = 1.0 - t; // 1 → 0 (separation)
@ -209,7 +207,11 @@ class VerificationSuccessAnimationState
final rotation = sep * 0.7; final rotation = sep * 0.7;
// Interpolated colour. // 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). // Pulse: 1 → 1.08 → 1 (smooth sine bump).
final pulseScale = 1.0 + math.sin(_pulse.value * math.pi) * 0.08; final pulseScale = 1.0 + math.sin(_pulse.value * math.pi) * 0.08;
@ -232,7 +234,9 @@ class VerificationSuccessAnimationState
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
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, blurRadius: widget.size * 0.45 * _glow.value,
spreadRadius: widget.size * 0.08 * _glow.value, spreadRadius: widget.size * 0.08 * _glow.value,
), ),
@ -247,7 +251,7 @@ class VerificationSuccessAnimationState
child: CustomPaint( child: CustomPaint(
painter: _RipplePainter( painter: _RipplePainter(
progress: _ripple.value, progress: _ripple.value,
color: _green, color: successColor,
maxRadius: widget.size * 0.45, maxRadius: widget.size * 0.45,
), ),
), ),
@ -323,7 +327,7 @@ class VerificationSuccessAnimationState
child: CustomPaint( child: CustomPaint(
painter: _SparklePainter( painter: _SparklePainter(
progress: _sparkle.value, progress: _sparkle.value,
color: _green, color: successColor,
maxRadius: widget.size * 0.4, maxRadius: widget.size * 0.4,
), ),
), ),

View file

@ -20,7 +20,10 @@ InputDecoration inputTextMessageDeco(BuildContext context, String hintText) {
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20), 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( spans.add(
TextSpan( TextSpan(
text: url, text: url,
style: const TextStyle( style: TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
decorationColor: Colors.white, decorationColor: widget.textColor,
), ),
recognizer: recognizer, recognizer: recognizer,
), ),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,41 +1,97 @@
import 'package:flutter/material.dart'; 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 { /// Every Twonly-specific color, with its light and dark values side by side.
static const messageSelf = Color.fromARGB(255, 58, 136, 102); ///
static const messageOther = Color.fromARGB(233, 68, 137, 255); /// 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( /// Resolves [AppColor] tokens for one theme and keeps theme transitions smooth.
Message message, @immutable
MediaFile? mediaFile, class AppColors extends ThemeExtension<AppColors> {
BuildContext context, const AppColors.light()
) { : _brightness = Brightness.light,
Color color; _interpolatedColors = null;
if (message.type == MessageType.restoreFlameCounter.name) { const AppColors.dark()
color = Colors.orange; : _brightness = Brightness.dark,
} else if (message.type == MessageType.text.name) { _interpolatedColors = null;
color = Colors.blueAccent;
} else if (message.isWidgetMedia) { const AppColors._interpolated(this._interpolatedColors) : _brightness = null;
color = const Color.fromARGB(255, 155, 89, 182);
} else if (mediaFile != null) { final Brightness? _brightness;
if (mediaFile.requiresAuthentication) { final Map<AppColor, Color>? _interpolatedColors;
color = context.color.primary;
} else { Color operator [](AppColor color) {
if (mediaFile.type == MediaType.video) { return _interpolatedColors?[color] ?? color.resolve(_brightness!);
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;
} }
@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 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/visual/themes/colors.dart';
import 'package:twonly/src/visual/themes/light.dart'; import 'package:twonly/src/visual/themes/light.dart';
ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) { Color _onPrimary(Color primary) {
final base = ThemeData.dark().copyWith( return ThemeData.estimateBrightnessForColor(primary) == Brightness.dark
colorScheme: ColorScheme.fromSeed( ? const Color(0xFFFFFFFF)
: const Color(0xFF082117);
}
ColorScheme _darkColorScheme(Color primary) {
return ColorScheme.fromSeed(
brightness: Brightness.dark, brightness: Brightness.dark,
seedColor: primary, seedColor: primary,
primary: primary, primary: primary,
surface: const Color.fromARGB(255, 20, 18, 23), ).copyWith(
surfaceContainer: const Color.fromARGB(255, 45, 41, 54), onPrimary: _onPrimary(primary),
surfaceContainerLow: const Color.fromARGB(255, 38, 34, 45), surface: const Color(0xFF111412),
surfaceContainerHigh: const Color.fromARGB(255, 52, 48, 62), 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( inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
extensions: const [AppColors.dark()],
); );
return base.copyWith( return base.copyWith(
textTheme: base.textTheme.apply( textTheme: base.textTheme.apply(

View file

@ -1,20 +1,56 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; 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]) { Color _onPrimary(Color primary) {
final base = ThemeData( return ThemeData.estimateBrightnessForColor(primary) == Brightness.dark
colorScheme: ColorScheme.fromSeed( ? 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, seedColor: primary,
primary: primary, primary: primary,
surface: Colors.white, ).copyWith(
), onPrimary: _onPrimary(primary),
scaffoldBackgroundColor: Colors.white, 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( inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
extensions: const [AppColors.light()],
); );
return base.copyWith( return base.copyWith(
textTheme: base.textTheme.apply( textTheme: base.textTheme.apply(
@ -28,20 +64,17 @@ final ThemeData lightTheme = getLightTheme();
final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom( final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom(
backgroundColor: defaultPrimaryColor, backgroundColor: defaultPrimaryColor,
foregroundColor: Colors.black87, foregroundColor: const Color(0xFF082117),
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
); );
ButtonStyle secondaryGreyButtonStyle(BuildContext context) { ButtonStyle secondaryGreyButtonStyle(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return FilledButton.styleFrom( return FilledButton.styleFrom(
backgroundColor: isDarkMode(context) ? Colors.grey[800] : Colors.grey[200], backgroundColor: colors.surfaceContainerHigh,
foregroundColor: isDarkMode(context) ? Colors.white : Colors.black87, foregroundColor: colors.onSurface,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(8),
),
); );
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/utils/log.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/action_button.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/layer_data.dart';
@ -171,7 +172,9 @@ class _EmojiLayerState extends State<EmojiLayer> {
child: ActionButton( child: ActionButton(
FontAwesomeIcons.trashCan, FontAwesomeIcons.trashCan,
tooltipText: '', 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:collection/collection.dart';
import 'package:flutter/material.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/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/datetime_filter.dart';
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/image_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, { this.text, {
super.key, super.key,
this.fontSize = 24, this.fontSize = 24,
this.color = Colors.white, this.color,
}); });
final String text; final String text;
final double fontSize; final double fontSize;
final Color color; final Color? color;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -58,10 +59,10 @@ class FilterText extends StatelessWidget {
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontSize: fontSize, fontSize: fontSize,
color: color, color: color ?? context.appColor(AppColor.mediaForeground),
shadows: const [ shadows: [
Shadow( Shadow(
color: Color.fromARGB(122, 0, 0, 0), color: context.appColor(AppColor.mediaScrim),
blurRadius: 5, 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'; import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart';
class DateTimeFilter extends StatelessWidget { 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 @override
Widget build(BuildContext context) { 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:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:twonly/src/providers/image_editor.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/action_button.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/layer_data.dart';
@ -113,7 +114,7 @@ class _TextViewState extends State<TextLayer> {
right: 0, right: 0,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withAlpha(100), color: context.appColor(AppColor.mediaScrim),
), ),
child: TextField( child: TextField(
controller: textController, controller: textController,
@ -151,8 +152,8 @@ class _TextViewState extends State<TextLayer> {
), ),
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
fontSize: 20, fontSize: 20,
), ),
), ),
@ -219,13 +220,13 @@ class _TextViewState extends State<TextLayer> {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withAlpha(100), color: context.appColor(AppColor.mediaScrim),
), ),
child: Text( child: Text(
widget.layerData.text, widget.layerData.text,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
fontSize: 20, fontSize: 20,
), ),
), ),
@ -245,7 +246,9 @@ class _TextViewState extends State<TextLayer> {
child: ActionButton( child: ActionButton(
FontAwesomeIcons.trashCan, FontAwesomeIcons.trashCan,
tooltipText: '', 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 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
/// Cuts a recorded video down to the part the user wants to send. /// Cuts a recorded video down to the part the user wants to send.
@ -183,7 +184,7 @@ class _VideoTrimmerState extends State<VideoTrimmer> {
decoration: BoxDecoration( decoration: BoxDecoration(
// Translucent rather than opaque: the frames being cut away stay // Translucent rather than opaque: the frames being cut away stay
// visible underneath, which is most of what makes a cut readable. // 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), borderRadius: BorderRadius.circular(16),
), ),
child: Column( child: Column(
@ -243,10 +244,15 @@ class _VideoTrimmerState extends State<VideoTrimmer> {
: _format(_duration), : _format(_duration),
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.white.withAlpha(isTrimmed ? 235 : 150), color: isTrimmed
? context.appColor(AppColor.mediaForeground)
: context.appColor(AppColor.mediaForegroundMuted),
fontFeatures: const [FontFeature.tabularFigures()], fontFeatures: const [FontFeature.tabularFigures()],
shadows: const [ shadows: [
Shadow(color: Color.fromARGB(122, 0, 0, 0), blurRadius: 4), Shadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 4,
),
], ],
), ),
), ),
@ -287,7 +293,9 @@ class _TrimTrack extends StatelessWidget {
right: handleWidth, right: handleWidth,
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withAlpha(46), color: context
.appColor(AppColor.mediaForeground)
.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
), ),
@ -307,8 +315,14 @@ class _TrimTrack extends StatelessWidget {
), ),
), ),
), ),
_handle(left: startOffset), _handle(
_handle(left: endOffset + handleWidth), left: startOffset,
foregroundColor: context.appColor(AppColor.mediaForeground),
),
_handle(
left: endOffset + handleWidth,
foregroundColor: context.appColor(AppColor.mediaForeground),
),
Positioned( Positioned(
left: playheadOffset + handleWidth - 1, left: playheadOffset + handleWidth - 1,
top: 4, top: 4,
@ -316,10 +330,13 @@ class _TrimTrack extends StatelessWidget {
width: 3, width: 3,
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
boxShadow: const [ boxShadow: [
BoxShadow(color: Color.fromARGB(122, 0, 0, 0), blurRadius: 4), 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( return Positioned(
left: left, left: left,
top: 0, top: 0,
@ -346,7 +363,7 @@ class _TrimTrack extends StatelessWidget {
width: 2, width: 2,
height: 14, height: 14,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withAlpha(220), color: foregroundColor,
borderRadius: BorderRadius.circular(1), borderRadius: BorderRadius.circular(1),
), ),
), ),

View file

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

View file

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

View file

@ -77,7 +77,7 @@ class _TypingIndicatorSubtitleCompState
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 3), padding: const EdgeInsets.symmetric(vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: getMessageColor(true), color: getMessageColor(context, true),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Transform.scale( 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/contact_groups.comp.dart';
import 'package:twonly/src/visual/components/flame_counter.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/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/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/blink.component.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_group_action.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) { Color getMessageColor(BuildContext context, bool isOther) {
return isOther ? DefaultColors.messageSelf : DefaultColors.messageOther; return isOther
? context.appColor(AppColor.chatBubbleReceived)
: context.appColor(AppColor.chatBubbleSent);
} }
class ChatItem { class ChatItem {

View file

@ -80,10 +80,10 @@ class _AllReactionsViewState extends State<AllReactionsView> {
topRight: Radius.circular(32), topRight: Radius.circular(32),
), ),
color: context.color.surface, color: context.color.surface,
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 10.9, 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), margin: const EdgeInsets.all(30),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
color: Colors.grey, color: context.color.outline,
), ),
height: 3, height: 3,
width: 60, width: 60,

View file

@ -30,10 +30,10 @@ class MessageHistoryView extends StatelessWidget {
topRight: Radius.circular(32), topRight: Radius.circular(32),
), ),
color: context.color.surface, color: context.color.surface,
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 10.9, 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), margin: const EdgeInsets.all(30),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
color: Colors.grey, color: context.color.outline,
), ),
height: 3, height: 3,
width: 60, width: 60,

View file

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

View file

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

View file

@ -161,13 +161,13 @@ class _ChatGroupActionState extends State<ChatGroupAction> {
child: FaIcon( child: FaIcon(
icon, icon,
size: 10, size: 10,
color: Colors.grey, color: context.color.onSurfaceVariant,
), ),
), ),
const WidgetSpan(child: SizedBox(width: 8)), const WidgetSpan(child: SizedBox(width: 8)),
TextSpan( TextSpan(
text: text, 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 // ignore: inference_failure_on_function_invocation
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
backgroundColor: Colors.black, backgroundColor: context.color.surface,
builder: (context) { builder: (context) {
return AllReactionsView( return AllReactionsView(
message: message, message: message,
@ -103,9 +103,7 @@ class ReactionRow extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(), border: Border.all(),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
color: isDarkMode(context) color: context.appColor(AppColor.reactionBackground),
? const Color.fromARGB(255, 74, 74, 74)
: const Color.fromARGB(255, 197, 197, 197),
), ),
child: Row( child: Row(
children: [ children: [
@ -119,9 +117,7 @@ class ReactionRow extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: isDarkMode(context) color: context.appColor(AppColor.onReaction),
? Colors.white
: Colors.black,
decoration: TextDecoration.none, decoration: TextDecoration.none,
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),

View file

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

View file

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

View file

@ -31,9 +31,7 @@ class ChatDateChip extends StatelessWidget {
return Center( return Center(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode(context) color: context.color.surfaceContainerHigh,
? const Color.fromARGB(255, 38, 38, 38)
: Colors.black.withAlpha(40),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
@ -43,7 +41,7 @@ class ChatDateChip extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 10, 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( text: context.lang.chatEntryFlameRestored(
data.restoredFlameCounter.toInt(), 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/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/elements/better_text.element.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/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/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'; import 'package:twonly/src/visual/views/chats/media_viewer.view.dart';
class ChatMediaEntry extends StatefulWidget { 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), padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode(context) ? Colors.black : Colors.grey, color: context.appColor(AppColor.chatBubbleDeleted),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: BetterText( child: BetterText(
text: context.lang.updateTwonlyMessage, text: context.lang.updateTwonlyMessage,
textColor: isDarkMode(context) textColor: context.appColor(AppColor.onChatBubbleDeleted),
? const Color.fromARGB(255, 99, 99, 99)
: Colors.black,
), ),
); );
} }

View file

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

View file

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

View file

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

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 = final layer =
await showModalBottomSheet( await showModalBottomSheet(
context: navigator.context, context: navigator.context,
backgroundColor: Colors.black, backgroundColor: context.color.surface,
builder: (context) { builder: (context) {
return const EmojiPickerBottom(); return const EmojiPickerBottom();
}, },

View file

@ -324,7 +324,7 @@ class _MessageInputState extends State<MessageInput>
// ignore: inference_failure_on_function_invocation // ignore: inference_failure_on_function_invocation
await showModalBottomSheet( await showModalBottomSheet(
context: context, context: context,
backgroundColor: Colors.black, backgroundColor: context.color.surface,
builder: (context) { builder: (context) {
return ShareAdditionalView( return ShareAdditionalView(
group: widget.group, group: widget.group,
@ -457,10 +457,12 @@ class _MessageInputState extends State<MessageInput>
), ),
child: FadeTransition( child: FadeTransition(
opacity: _recordingBlink, opacity: _recordingBlink,
child: const FaIcon( child: FaIcon(
FontAwesomeIcons.microphone, FontAwesomeIcons.microphone,
size: 20, size: 20,
color: Colors.red, color: context.appColor(
AppColor.recording,
),
), ),
), ),
), ),
@ -470,9 +472,7 @@ class _MessageInputState extends State<MessageInput>
_currentDuration, _currentDuration,
), ),
style: TextStyle( style: TextStyle(
color: isDarkMode(context) color: context.color.onSurface,
? Colors.white
: Colors.black,
fontSize: 12, fontSize: 12,
), ),
), ),
@ -491,8 +491,10 @@ class _MessageInputState extends State<MessageInput>
onTap: _cancelAudioRecording, onTap: _cancelAudioRecording,
child: Text( child: Text(
context.lang.voiceMessageCancel, context.lang.voiceMessageCancel,
style: const TextStyle( style: TextStyle(
color: Colors.red, color: context.appColor(
AppColor.recording,
),
), ),
), ),
), ),
@ -591,9 +593,7 @@ class _MessageInputState extends State<MessageInput>
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
90, 90,
), ),
color: isDarkMode(context) color: context.color.surface,
? Colors.black
: Colors.white,
), ),
child: const Center( child: const Center(
child: Column( child: Column(
@ -622,7 +622,9 @@ class _MessageInputState extends State<MessageInput>
right: -20, right: -20,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red, color: context.appColor(
AppColor.recording,
),
borderRadius: BorderRadius.circular(90), borderRadius: BorderRadius.circular(90),
), ),
width: 60, width: 60,
@ -644,7 +646,7 @@ class _MessageInputState extends State<MessageInput>
color: color:
(_recordingState == (_recordingState ==
RecordingState.recording) RecordingState.recording)
? Colors.white ? context.color.onError
: null, : null,
(_recordingState == RecordingState.none) (_recordingState == RecordingState.none)
? FontAwesomeIcons.microphone ? FontAwesomeIcons.microphone
@ -712,11 +714,11 @@ class _MessageInputState extends State<MessageInput>
), ),
searchViewConfig: SearchViewConfig( searchViewConfig: SearchViewConfig(
backgroundColor: context.color.surfaceContainer, backgroundColor: context.color.surfaceContainer,
buttonIconColor: Colors.white, buttonIconColor: context.color.onSurface,
), ),
categoryViewConfig: CategoryViewConfig( categoryViewConfig: CategoryViewConfig(
backgroundColor: context.color.surfaceContainer, backgroundColor: context.color.surfaceContainer,
dividerColor: Colors.white, dividerColor: context.color.outlineVariant,
indicatorColor: context.color.primary, indicatorColor: context.color.primary,
iconColorSelected: context.color.primary, iconColorSelected: context.color.primary,
iconColor: context.color.secondary, 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/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/animate_icon.comp.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/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/file_limit_reached.dialog.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_color.dart';
enum MessageSendState { enum MessageSendState {
received, received,
@ -310,7 +310,11 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
widget.group!.draftMessage != null && widget.group!.draftMessage != null &&
widget.group!.draftMessage != '') { widget.group!.draftMessage != '') {
icons = [ icons = [
const FaIcon(FontAwesomeIcons.pen, size: 12, color: Colors.grey), FaIcon(
FontAwesomeIcons.pen,
size: 12,
color: context.color.onSurfaceVariant,
),
]; ];
textWidget = Text( textWidget = Text(
'${context.lang.draftMessage}: ${substringBy(widget.group!.draftMessage!, 10)}', '${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, maxWidth: MediaQuery.sizeOf(context).width * 0.8,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: getMessageColor(msg.senderId != null), color: getMessageColor(context, msg.senderId != null),
borderRadius: borderRadius, borderRadius: borderRadius,
), ),
child: IntrinsicWidth( child: IntrinsicWidth(
@ -187,7 +187,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String? subtitle; String? subtitle;
var color = const Color.fromARGB(233, 68, 137, 255); var color = context.appColor(AppColor.messageText);
if (_message != null) { if (_message != null) {
if (_message!.type == MessageType.text.name) { if (_message!.type == MessageType.text.name) {
@ -238,7 +238,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
subtitle = _storyAlive ? context.lang.story : context.lang.storyExpired; subtitle = _storyAlive ? context.lang.story : context.lang.storyExpired;
} }
color = getMessageColor(_message!.senderId != null); color = getMessageColor(context, _message!.senderId != null);
} }
final hasImage = final hasImage =

View file

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

View file

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

View file

@ -3,6 +3,7 @@ import 'package:photo_view/photo_view.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart' import 'package:twonly/src/database/tables/mediafiles.table.dart'
show MediaType; show MediaType;
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
class MediaContentRenderer extends StatelessWidget { class MediaContentRenderer extends StatelessWidget {
@ -47,10 +48,10 @@ class MediaContentRenderer extends StatelessWidget {
initialScale: PhotoViewComputedScale.contained, initialScale: PhotoViewComputedScale.contained,
minScale: PhotoViewComputedScale.contained, minScale: PhotoViewComputedScale.contained,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return const Center( return Center(
child: Icon( child: Icon(
Icons.broken_image_outlined, Icons.broken_image_outlined,
color: Colors.white38, color: context.appColor(AppColor.mediaForegroundMuted),
size: 64, 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/locator.dart';
import 'package:twonly/src/database/daos/stories.dao.dart'; import 'package:twonly/src/database/daos/stories.dao.dart';
import 'package:twonly/src/database/twonly.db.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/elements/my_icon_button.element.dart';
import 'package:twonly/src/visual/views/chats/media_viewer_components/story_viewers.bottom_sheet.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 { Future<void> _showViewers(BuildContext context, MediaFile media) async {
await showModalBottomSheet<void>( await showModalBottomSheet<void>(
context: context, context: context,
backgroundColor: Colors.black, backgroundColor: context.appColor(AppColor.mediaBackground),
builder: (context) => StoryViewersBottomSheet(mediaFile: media), builder: (context) => StoryViewersBottomSheet(mediaFile: media),
); );
} }

View file

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

View file

@ -94,10 +94,10 @@ class _StoryViewersBottomSheetState extends State<StoryViewersBottomSheet> {
topRight: Radius.circular(32), topRight: Radius.circular(32),
), ),
color: context.color.surface, color: context.color.surface,
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 10.9, 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), margin: const EdgeInsets.only(top: 20, bottom: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
color: Colors.grey, color: context.color.outline,
), ),
height: 3, height: 3,
width: 60, width: 60,

View file

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

View file

@ -52,9 +52,9 @@ class OpenRequestsListComp extends StatelessWidget {
).merge(secondaryGreyButtonStyle(context)), ).merge(secondaryGreyButtonStyle(context)),
child: Row( child: Row(
children: [ children: [
const Icon( Icon(
Icons.person_off_rounded, Icons.person_off_rounded,
color: Color.fromARGB(164, 244, 67, 54), color: context.color.error,
), ),
Text( Text(
context.lang.contactActionBlock, context.lang.contactActionBlock,
@ -89,7 +89,7 @@ class OpenRequestsListComp extends StatelessWidget {
).merge(secondaryGreyButtonStyle(context)), ).merge(secondaryGreyButtonStyle(context)),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.check, color: Colors.green), Icon(Icons.check, color: context.appColor(AppColor.success)),
Text( Text(
context.lang.contactActionAccept, context.lang.contactActionAccept,
style: const TextStyle(fontSize: 10), 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/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/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/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/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/contact/select_contact_groups.view.dart';
import 'package:twonly/src/visual/views/groups/group.view.dart'; import 'package:twonly/src/visual/views/groups/group.view.dart';
@ -301,9 +300,6 @@ class _ContactViewState extends State<ContactView> {
MutualGroupsExpansionTileComp( MutualGroupsExpansionTileComp(
contact: contact, contact: contact,
), ),
UserDiscoveryContactSettingsComp(
contact: contact,
),
const Divider(), const Divider(),
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.flag, icon: FontAwesomeIcons.flag,
@ -331,7 +327,7 @@ class _ContactViewState extends State<ContactView> {
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.userMinus, icon: FontAwesomeIcons.userMinus,
iconSize: 16, iconSize: 16,
color: Colors.red, color: context.color.error,
text: context.lang.contactRemove, text: context.lang.contactRemove,
onTap: () => handleUserRemoveRequest(contact), onTap: () => handleUserRemoveRequest(contact),
), ),
@ -382,12 +378,16 @@ class _ContactViewState extends State<ContactView> {
SignalVersion.v1 => 'Signal Protocol (v1)', SignalVersion.v1 => 'Signal Protocol (v1)',
SignalVersion.v2 => 'PQXDH (v2)', SignalVersion.v2 => 'PQXDH (v2)',
}}', }}',
style: const TextStyle(color: Colors.grey), style: TextStyle(
color: context.color.onSurfaceVariant,
),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Can send widgets: ${contact.widgetSharingAllowed ? 'Yes' : 'No'}', '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 { Future<void> _selectEmoji() async {
final result = await showModalBottomSheet<dynamic>( final result = await showModalBottomSheet<dynamic>(
context: context, context: context,
backgroundColor: Colors.black, backgroundColor: context.color.surface,
builder: (context) => const EmojiPickerBottom(), builder: (context) => const EmojiPickerBottom(),
); );
if (result is EmojiLayerData && mounted) { if (result is EmojiLayerData && mounted) {
@ -330,9 +330,7 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
color: color == noContactGroupBackgroundColor ? null : Color(color), color: color == noContactGroupBackgroundColor ? null : Color(color),
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: selected color: selected ? context.color.onSurface : context.color.outline,
? (isDarkMode(context) ? Colors.white : Colors.black)
: Theme.of(context).colorScheme.outline,
width: selected ? 3 : 1, width: selected ? 3 : 1,
), ),
), ),
@ -431,10 +429,10 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
IconButton( IconButton(
tooltip: context.lang.deleteContactGroup, tooltip: context.lang.deleteContactGroup,
onPressed: _delete, onPressed: _delete,
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.trashCan, FontAwesomeIcons.trashCan,
size: 18, size: 18,
color: Colors.red, color: context.color.error,
), ),
), ),
], ],

View file

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

View file

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

View file

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

View file

@ -51,11 +51,11 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
width: 120, width: 120,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: const [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black12, color: context.color.shadow.withValues(alpha: 0.12),
blurRadius: 6, blurRadius: 6,
offset: Offset(0, 2), offset: const Offset(0, 2),
), ),
], ],
), ),
@ -68,11 +68,15 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return ColoredBox( return ColoredBox(
color: Colors.grey.shade800, color: context.appColor(
child: const Center( AppColor.mediaBackground,
),
child: Center(
child: Icon( child: Icon(
Icons.broken_image_outlined, Icons.broken_image_outlined,
color: Colors.white30, color: context.appColor(
AppColor.mediaForegroundMuted,
),
size: 32, size: 32,
), ),
), ),
@ -86,7 +90,7 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
begin: Alignment.bottomCenter, begin: Alignment.bottomCenter,
end: Alignment.center, end: Alignment.center,
colors: [ colors: [
Colors.black.withValues(alpha: 0.7), context.appColor(AppColor.mediaScrim),
Colors.transparent, Colors.transparent,
], ],
), ),
@ -100,8 +104,10 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
child: Text( child: Text(
text, text,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
color: Colors.white, color: context.appColor(
AppColor.mediaForeground,
),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14, 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/model/memory_item.model.dart';
import 'package:twonly/src/services/memories/memories_cloud.service.dart'; import 'package:twonly/src/services/memories/memories_cloud.service.dart';
import 'package:twonly/src/utils/log.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/components/selectable_thumbnail.comp.dart';
import 'package:twonly/src/visual/views/memories/components/memory_transition_painter.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); Log.warn(error);
return ColoredBox( return ColoredBox(
color: Colors.grey.shade200, color: context.color.surfaceContainerHigh,
child: const Center( child: Center(
child: FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: Colors.black26, color: context.color.onSurfaceVariant,
), ),
), ),
); );
@ -251,37 +252,43 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
) )
else else
ColoredBox( ColoredBox(
color: Colors.grey.shade200, color: context.color.surfaceContainerHigh,
child: const Center( child: Center(
child: FaIcon( child: FaIcon(
FontAwesomeIcons.image, FontAwesomeIcons.image,
color: Colors.black26, color: context.color.onSurfaceVariant,
), ),
), ),
), ),
if (isVideo) if (isVideo)
const Positioned.fill( Positioned.fill(
child: Center( child: Center(
child: FaIcon( child: FaIcon(
FontAwesomeIcons.circlePlay, FontAwesomeIcons.circlePlay,
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
size: 32, size: 32,
shadows: [ shadows: [
Shadow(color: Colors.black54, blurRadius: 6), Shadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 6,
),
], ],
), ),
), ),
), ),
if (media.mediaFile.isFavorite) if (media.mediaFile.isFavorite)
const Positioned( Positioned(
bottom: 6, bottom: 6,
left: 6, left: 6,
child: Icon( child: Icon(
Icons.favorite, Icons.favorite,
color: Colors.redAccent, color: context.color.error,
size: 16, size: 16,
shadows: [ 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) { switch (media.mediaFile.cloudState) {
case CloudState.none: case CloudState.none:
iconData = Icons.cloud_off_outlined; iconData = Icons.cloud_off_outlined;
color = Colors.white54; color = context.appColor(AppColor.mediaForegroundMuted);
case CloudState.pending: case CloudState.pending:
iconData = Icons.cloud_upload_outlined; iconData = Icons.cloud_upload_outlined;
color = Colors.white70; color = context.appColor(AppColor.mediaForegroundMuted);
case CloudState.uploaded: case CloudState.uploaded:
if (hasStored) { if (hasStored) {
iconData = Icons.cloud_done_outlined; iconData = Icons.cloud_done_outlined;
color = Colors.white; color = context.appColor(AppColor.mediaForeground);
} else { } else {
iconData = Icons.cloud_outlined; iconData = Icons.cloud_outlined;
color = Colors.white; color = context.appColor(AppColor.mediaForeground);
} }
} }
@ -315,8 +322,11 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
iconData, iconData,
color: color, color: color,
size: 16, size: 16,
shadows: const [ shadows: [
Shadow(color: Colors.black54, blurRadius: 4), Shadow(
color: context.appColor(AppColor.mediaScrim),
blurRadius: 4,
),
], ],
), ),
); );

View file

@ -85,15 +85,15 @@ class MemoriesSelectionMenuComp extends StatelessWidget {
value: 'delete', value: 'delete',
child: Row( child: Row(
children: [ children: [
const Icon( Icon(
Icons.delete_forever_outlined, Icons.delete_forever_outlined,
size: 20, size: 20,
color: Colors.redAccent, color: context.color.error,
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Text( Text(
context.lang.memoriesMenuDelete, 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), padding: const EdgeInsets.all(12),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.50), color: context.appColor(AppColor.mediaScrim),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
@ -40,26 +40,28 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
if (showStoreButton) if (showStoreButton)
_ToolbarAction( _ToolbarAction(
icon: isImageSaving icon: isImageSaving
? const SizedBox( ? SizedBox(
width: 16, width: 16,
height: 16, height: 16,
child: CircularProgressIndicator.adaptive( child: CircularProgressIndicator.adaptive(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.white), valueColor: AlwaysStoppedAnimation(
context.appColor(AppColor.mediaForeground),
),
), ),
) )
: const FaIcon( : FaIcon(
FontAwesomeIcons.floppyDisk, FontAwesomeIcons.floppyDisk,
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
size: 18, size: 18,
), ),
label: context.lang.galleryActionSave, label: context.lang.galleryActionSave,
onTap: isImageSaving ? null : onStore, onTap: isImageSaving ? null : onStore,
), ),
_ToolbarAction( _ToolbarAction(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.download, FontAwesomeIcons.download,
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
size: 19, size: 19,
), ),
label: context.lang.galleryActionExport, label: context.lang.galleryActionExport,
@ -68,7 +70,9 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
_ToolbarAction( _ToolbarAction(
icon: Icon( icon: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border, isFavorite ? Icons.favorite : Icons.favorite_border,
color: isFavorite ? Colors.redAccent : Colors.white, color: isFavorite
? context.color.error
: context.appColor(AppColor.mediaForeground),
size: 22, size: 22,
), ),
label: isFavorite label: isFavorite
@ -77,18 +81,18 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
onTap: onToggleFavorite, onTap: onToggleFavorite,
), ),
_ToolbarAction( _ToolbarAction(
icon: const Icon( icon: Icon(
Icons.delete, Icons.delete,
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
size: 22, size: 22,
), ),
label: context.lang.galleryActionDelete, label: context.lang.galleryActionDelete,
onTap: onDelete, onTap: onDelete,
), ),
_ToolbarAction( _ToolbarAction(
icon: const FaIcon( icon: FaIcon(
FontAwesomeIcons.shareNodes, FontAwesomeIcons.shareNodes,
color: Colors.white, color: context.appColor(AppColor.mediaForeground),
size: 19, size: 19,
), ),
label: context.lang.galleryActionShare, label: context.lang.galleryActionShare,
@ -135,7 +139,7 @@ class _ToolbarAction extends StatelessWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: Colors.white.withValues(alpha: 0.9), color: context.appColor(AppColor.mediaForegroundMuted),
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
letterSpacing: 0.1, 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/database/tables/mediafiles.table.dart';
import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/services/memories/memories_cloud.service.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'; import 'package:twonly/src/visual/helpers/video_player_file.helper.dart';
class SynchronizedViewerItemComp extends StatefulWidget { class SynchronizedViewerItemComp extends StatefulWidget {
@ -124,10 +125,10 @@ class _SynchronizedViewerItemCompState
hash: item.mediaService.mediaFile.blurhash!, hash: item.mediaService.mediaFile.blurhash!,
optimizationMode: BlurHashOptimizationMode.approximation, optimizationMode: BlurHashOptimizationMode.approximation,
) )
: const Center( : Center(
child: Icon( child: Icon(
Icons.broken_image_outlined, Icons.broken_image_outlined,
color: Colors.white38, color: context.appColor(AppColor.mediaForegroundMuted),
size: 64, size: 64,
), ),
) )
@ -143,10 +144,10 @@ class _SynchronizedViewerItemCompState
color: Colors.transparent, color: Colors.transparent,
), ),
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return const Center( return Center(
child: Icon( child: Icon(
Icons.broken_image_outlined, Icons.broken_image_outlined,
color: Colors.white38, color: context.appColor(AppColor.mediaForegroundMuted),
size: 64, size: 64,
), ),
); );

View file

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

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