mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
use single file to manage all colors
This commit is contained in:
parent
75df8840ae
commit
8580908536
138 changed files with 1183 additions and 873 deletions
|
|
@ -14,11 +14,17 @@ import 'package:twonly/src/model/error_code.dart';
|
|||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/backup.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/themes/colors.dart';
|
||||
|
||||
export 'package:twonly/src/visual/themes/colors.dart' show AppColor;
|
||||
|
||||
extension ShortCutsExtension on BuildContext {
|
||||
AppLocalizations get lang => AppLocalizations.of(this)!;
|
||||
ColorScheme get color => Theme.of(this).colorScheme;
|
||||
Color appColor(AppColor color) {
|
||||
return Theme.of(this).extension<AppColors>()![color];
|
||||
}
|
||||
|
||||
Future<dynamic> navPush(Widget route) async {
|
||||
return Navigator.push(
|
||||
this,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withAlpha(100),
|
||||
color: context.color.errorContainer,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
|
|
@ -71,7 +71,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
|
|||
textAlign: TextAlign.center,
|
||||
softWrap: true,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
color: context.color.onErrorContainer,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
|
@ -90,7 +90,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
color: context.color.error,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
|
|
@ -101,7 +101,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
|
|||
textAlign: TextAlign.center,
|
||||
softWrap: true,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
color: context.color.onError,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
|
@ -123,7 +123,7 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
|
|||
child: Text(
|
||||
context.lang.appOutdatedBtn,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.white,
|
||||
color: context.color.onSurface,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ class ConnectionStatusComp extends StatelessWidget {
|
|||
return Stack(
|
||||
children: [
|
||||
if (!isConnected)
|
||||
const Positioned.fill(
|
||||
Positioned.fill(
|
||||
child: SpinKitRipple(
|
||||
color: Colors.red,
|
||||
color: context.color.error,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
|
|
@ -29,7 +29,7 @@ class ConnectionStatusComp extends StatelessWidget {
|
|||
border: Border.all(
|
||||
color: isConnected
|
||||
? context.color.primary.withAlpha(100)
|
||||
: Colors.red,
|
||||
: context.color.error,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(0.5),
|
||||
|
|
|
|||
|
|
@ -34,13 +34,11 @@ class ContactRequestBadgeComp extends StatelessWidget {
|
|||
),
|
||||
Center(
|
||||
child: NotificationBadgeComp(
|
||||
backgroundColor: isDarkMode(context)
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
textColor: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
backgroundColor: context.color.inverseSurface,
|
||||
textColor: context.color.onInverseSurface,
|
||||
count: count.toString(),
|
||||
child: IconButton(
|
||||
color: Colors.black,
|
||||
color: context.color.onPrimary,
|
||||
icon: const FaIcon(
|
||||
FontAwesomeIcons.userPlus,
|
||||
size: 18,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
typedef TextLabelBuilder = Widget Function(String label);
|
||||
|
||||
|
|
@ -99,14 +100,9 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
|
|||
child: ValueListenableBuilder<double>(
|
||||
valueListenable: _thumbOffsetNotifier,
|
||||
builder: (context, thumbOffset, child) {
|
||||
final isDark =
|
||||
Theme.of(context).brightness == Brightness.dark;
|
||||
final handleColor = isDark
|
||||
? Colors.grey.shade900
|
||||
: Colors.white;
|
||||
final iconColor = isDark
|
||||
? Colors.white70
|
||||
: Colors.black54;
|
||||
final colors = context.color;
|
||||
final handleColor = colors.surfaceContainerLowest;
|
||||
final iconColor = colors.onSurfaceVariant;
|
||||
|
||||
final label = widget.labelBuilder?.call(
|
||||
_viewOffsetNotifier.value,
|
||||
|
|
@ -145,17 +141,14 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
|
|||
horizontal: 12,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
(isDark
|
||||
? Colors.grey.shade900
|
||||
: Colors.grey.shade200)
|
||||
color: colors.surfaceContainerHigh
|
||||
.withValues(alpha: 0.95),
|
||||
borderRadius: BorderRadius.circular(
|
||||
8,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(
|
||||
color: colors.shadow.withValues(
|
||||
alpha: 0.2,
|
||||
),
|
||||
blurRadius: 8,
|
||||
|
|
@ -166,9 +159,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
|
|||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isDark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
color: colors.onSurface,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
|
@ -182,9 +173,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar>
|
|||
decoration: BoxDecoration(
|
||||
color: handleColor,
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white10
|
||||
: Colors.black12,
|
||||
color: colors.outlineVariant,
|
||||
width: 0.5,
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class EmojiPickerBottom extends StatelessWidget {
|
|||
margin: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
color: context.color.outline,
|
||||
),
|
||||
height: 3,
|
||||
width: 60,
|
||||
|
|
@ -66,7 +66,7 @@ class EmojiPickerBottom extends StatelessWidget {
|
|||
),
|
||||
searchViewConfig: SearchViewConfig(
|
||||
backgroundColor: context.color.surfaceContainer,
|
||||
buttonIconColor: Colors.white,
|
||||
buttonIconColor: context.color.onSurface,
|
||||
),
|
||||
categoryViewConfig: CategoryViewConfig(
|
||||
backgroundColor: context.color.surfaceContainer,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class NotificationBadgeComp extends StatelessWidget {
|
||||
const NotificationBadgeComp({
|
||||
required this.count,
|
||||
required this.child,
|
||||
this.backgroundColor = Colors.red,
|
||||
this.textColor = Colors.white,
|
||||
this.backgroundColor,
|
||||
this.textColor,
|
||||
super.key,
|
||||
});
|
||||
final String count;
|
||||
final Color backgroundColor;
|
||||
final Color textColor;
|
||||
final Color? backgroundColor;
|
||||
final Color? textColor;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
|
|
@ -27,14 +28,14 @@ class NotificationBadgeComp extends StatelessWidget {
|
|||
height: 18,
|
||||
width: 18,
|
||||
child: CircleAvatar(
|
||||
backgroundColor: backgroundColor,
|
||||
backgroundColor: backgroundColor ?? context.color.error,
|
||||
child: Center(
|
||||
child: Transform.rotate(
|
||||
angle: infinity ? 90 * (3.141592653589793 / 180) : 0,
|
||||
child: Text(
|
||||
infinity ? '8' : count,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
color: textColor ?? context.color.onError,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -143,13 +143,13 @@ class _ProfileQrCodeCompState extends State<ProfileQrCodeComp> {
|
|||
? Container(
|
||||
key: const ValueKey('qr_code_container'),
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
color: context.appColor(AppColor.qrBackground),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
color: context.color.shadow.withValues(alpha: 0.2),
|
||||
blurRadius: 3,
|
||||
offset: Offset(0, 2),
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -159,11 +159,11 @@ class _ProfileQrCodeCompState extends State<ProfileQrCodeComp> {
|
|||
errorCorrectLevel: QrErrorCorrectLevel.M,
|
||||
),
|
||||
eyeStyle: QrEyeStyle(
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
color: context.appColor(AppColor.qrForeground),
|
||||
borderRadius: 2,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||
color: context.appColor(AppColor.qrForeground),
|
||||
borderRadius: 2,
|
||||
),
|
||||
gapless: false,
|
||||
|
|
@ -173,7 +173,7 @@ class _ProfileQrCodeCompState extends State<ProfileQrCodeComp> {
|
|||
embeddedImageStyle: QrEmbeddedImageStyle(
|
||||
size: const Size(60, 66),
|
||||
embeddedImageShape: EmbeddedImageShape.square,
|
||||
shapeColor: context.color.primary,
|
||||
shapeColor: context.appColor(AppColor.qrBackground),
|
||||
safeArea: true,
|
||||
),
|
||||
size: widget.size,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class _SelectChatDeletionTimeListTitleState
|
|||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
// Provide a background color for the popup.
|
||||
color: CupertinoColors.systemBackground.resolveFrom(context),
|
||||
color: context.color.surface,
|
||||
// Use a SafeArea widget to avoid system overlaps.
|
||||
child: SafeArea(top: false, child: child),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ class SelectableThumbnailComp extends StatelessWidget {
|
|||
curve: Curves.easeInOut,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? context.color.primary : Colors.transparent,
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
color: context.color.shadow.withValues(alpha: 0.12),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -49,20 +49,20 @@ class SelectableThumbnailComp extends StatelessWidget {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? context.color.primary : Colors.black38,
|
||||
color: isSelected
|
||||
? context.color.primary
|
||||
: context.color.scrim.withValues(alpha: 0.38),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
color: context.color.onSurface,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: isSelected
|
||||
? const Icon(
|
||||
? Icon(
|
||||
Icons.check,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
color: context.color.onPrimary,
|
||||
)
|
||||
: const SizedBox(width: 14, height: 14),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
enum SnackbarLevel {
|
||||
info,
|
||||
|
|
@ -14,23 +15,28 @@ void showSnackbar(
|
|||
String message, {
|
||||
SnackbarLevel level = SnackbarLevel.error,
|
||||
}) {
|
||||
Color backgroundColor;
|
||||
IconData iconData;
|
||||
|
||||
switch (level) {
|
||||
case SnackbarLevel.info:
|
||||
backgroundColor = Colors.blue.shade700;
|
||||
iconData = Icons.info_outline;
|
||||
case SnackbarLevel.success:
|
||||
backgroundColor = Colors.green.shade700;
|
||||
iconData = Icons.check_circle_outline;
|
||||
case SnackbarLevel.warning:
|
||||
backgroundColor = Colors.orange.shade800;
|
||||
iconData = Icons.warning_amber_rounded;
|
||||
case SnackbarLevel.error:
|
||||
backgroundColor = Colors.red.shade700;
|
||||
iconData = Icons.error_outline;
|
||||
}
|
||||
final (backgroundColor, foregroundColor, iconData) = switch (level) {
|
||||
SnackbarLevel.info => (
|
||||
context.appColor(AppColor.info),
|
||||
context.appColor(AppColor.onInfo),
|
||||
Icons.info_outline,
|
||||
),
|
||||
SnackbarLevel.success => (
|
||||
context.appColor(AppColor.success),
|
||||
context.appColor(AppColor.onSuccess),
|
||||
Icons.check_circle_outline,
|
||||
),
|
||||
SnackbarLevel.warning => (
|
||||
context.appColor(AppColor.warning),
|
||||
context.appColor(AppColor.onWarning),
|
||||
Icons.warning_amber_rounded,
|
||||
),
|
||||
SnackbarLevel.error => (
|
||||
context.color.error,
|
||||
context.color.onError,
|
||||
Icons.error_outline,
|
||||
),
|
||||
};
|
||||
|
||||
AnimationController? localAnimationController;
|
||||
|
||||
|
|
@ -44,7 +50,8 @@ void showSnackbar(
|
|||
child: _SnackbarWidget(
|
||||
message: message,
|
||||
backgroundColor: backgroundColor,
|
||||
icon: Icon(iconData, color: Colors.white, size: 28),
|
||||
foregroundColor: foregroundColor,
|
||||
icon: Icon(iconData, color: foregroundColor, size: 28),
|
||||
onCloseClick: () {
|
||||
localAnimationController?.reverse();
|
||||
},
|
||||
|
|
@ -101,11 +108,13 @@ class _SnackbarWidget extends StatelessWidget {
|
|||
const _SnackbarWidget({
|
||||
required this.message,
|
||||
required this.backgroundColor,
|
||||
required this.foregroundColor,
|
||||
required this.icon,
|
||||
required this.onCloseClick,
|
||||
});
|
||||
final String message;
|
||||
final Color backgroundColor;
|
||||
final Color foregroundColor;
|
||||
final Icon icon;
|
||||
final VoidCallback onCloseClick;
|
||||
|
||||
|
|
@ -118,9 +127,9 @@ class _SnackbarWidget extends StatelessWidget {
|
|||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
color: Theme.of(context).colorScheme.shadow.withValues(alpha: 0.2),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 30,
|
||||
),
|
||||
|
|
@ -138,10 +147,10 @@ class _SnackbarWidget extends StatelessWidget {
|
|||
child: Text(
|
||||
message,
|
||||
style: theme.textTheme.bodyMedium?.merge(
|
||||
const TextStyle(
|
||||
TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
color: foregroundColor,
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
|
|
@ -151,9 +160,13 @@ class _SnackbarWidget extends StatelessWidget {
|
|||
GestureDetector(
|
||||
onTap: onCloseClick,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Icon(Icons.close, color: Colors.white70, size: 20),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
color: foregroundColor.withValues(alpha: 0.75),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import 'package:twonly/src/constants/routes.keys.dart';
|
|||
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge_info.comp.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
||||
|
||||
class VerificationBadgeComp extends StatefulWidget {
|
||||
|
|
@ -198,7 +198,7 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
|||
? SvgIcons.verifiedNumeric(_verifiedByTransferredTrustCount)
|
||||
: SvgIcons.verifiedRed,
|
||||
color: (_verifiedByTransferredTrustCount > 0 && !_isVerified)
|
||||
? colorVerificationBadgeYellow
|
||||
? context.appColor(AppColor.verificationPending)
|
||||
: null,
|
||||
size: widget.size,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
|||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
||||
|
||||
const colorVerificationBadgeYellow = Color.fromARGB(255, 0, 182, 238);
|
||||
|
||||
class VerificationBadgeInfo extends StatelessWidget {
|
||||
const VerificationBadgeInfo({
|
||||
this.displayButtons = false,
|
||||
|
|
@ -121,19 +119,19 @@ class VerificationBadgeInfo extends StatelessWidget {
|
|||
),
|
||||
_buildItem(
|
||||
context,
|
||||
icon: const SvgIcon(
|
||||
icon: SvgIcon(
|
||||
assetPath: SvgIcons.verifiedGreen,
|
||||
size: 40,
|
||||
color: colorVerificationBadgeYellow,
|
||||
color: context.appColor(AppColor.verificationPending),
|
||||
),
|
||||
description: context.lang.verificationBadgeYellowDesc,
|
||||
boldTextColor: colorVerificationBadgeYellow,
|
||||
boldTextColor: context.appColor(AppColor.verificationPending),
|
||||
),
|
||||
_buildItem(
|
||||
context,
|
||||
icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40),
|
||||
description: context.lang.verificationBadgeRedDesc,
|
||||
boldTextColor: const Color(0xffff0000),
|
||||
boldTextColor: context.color.error,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'dart:math' as math;
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:vector_graphics/vector_graphics.dart';
|
||||
|
||||
/// Animated chain-link logo for the "verification success" moment.
|
||||
|
|
@ -85,9 +85,6 @@ class VerificationSuccessAnimationState
|
|||
static const _svg2 =
|
||||
'<svg viewBox="0 0 640 640"><path d="$_path2" fill="white"/></svg>';
|
||||
|
||||
static const _grey = Color(0xFF8E9AAF);
|
||||
static const Color _green = defaultPrimaryColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -196,6 +193,7 @@ class VerificationSuccessAnimationState
|
|||
animation: Listenable.merge([_ctrl, _idleCtrl]),
|
||||
builder: (context, _) {
|
||||
final scale = widget.size / 640;
|
||||
final successColor = context.color.primary;
|
||||
final t = _flyIn.value; // 0 → 1 (fly-in progress)
|
||||
final sep = 1.0 - t; // 1 → 0 (separation)
|
||||
|
||||
|
|
@ -209,7 +207,11 @@ class VerificationSuccessAnimationState
|
|||
final rotation = sep * 0.7;
|
||||
|
||||
// Interpolated colour.
|
||||
final color = Color.lerp(_grey, _green, _colorFade.value)!;
|
||||
final color = Color.lerp(
|
||||
context.appColor(AppColor.verificationIdle),
|
||||
successColor,
|
||||
_colorFade.value,
|
||||
)!;
|
||||
|
||||
// Pulse: 1 → 1.08 → 1 (smooth sine bump).
|
||||
final pulseScale = 1.0 + math.sin(_pulse.value * math.pi) * 0.08;
|
||||
|
|
@ -232,7 +234,9 @@ class VerificationSuccessAnimationState
|
|||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _green.withValues(alpha: 0.3 * _glow.value),
|
||||
color: successColor.withValues(
|
||||
alpha: 0.3 * _glow.value,
|
||||
),
|
||||
blurRadius: widget.size * 0.45 * _glow.value,
|
||||
spreadRadius: widget.size * 0.08 * _glow.value,
|
||||
),
|
||||
|
|
@ -247,7 +251,7 @@ class VerificationSuccessAnimationState
|
|||
child: CustomPaint(
|
||||
painter: _RipplePainter(
|
||||
progress: _ripple.value,
|
||||
color: _green,
|
||||
color: successColor,
|
||||
maxRadius: widget.size * 0.45,
|
||||
),
|
||||
),
|
||||
|
|
@ -323,7 +327,7 @@ class VerificationSuccessAnimationState
|
|||
child: CustomPaint(
|
||||
painter: _SparklePainter(
|
||||
progress: _sparkle.value,
|
||||
color: _green,
|
||||
color: successColor,
|
||||
maxRadius: widget.size * 0.4,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ InputDecoration inputTextMessageDeco(BuildContext context, String hintText) {
|
|||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderSide: const BorderSide(color: Colors.grey, width: 2),
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,9 +90,9 @@ class _BetterTextState extends State<BetterText> {
|
|||
spans.add(
|
||||
TextSpan(
|
||||
text: url,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: Colors.white,
|
||||
decorationColor: widget.textColor,
|
||||
),
|
||||
recognizer: recognizer,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ class ContactChip extends StatelessWidget {
|
|||
],
|
||||
if (onTap != null && contact != null) ...[
|
||||
const SizedBox(width: 15),
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.xmark,
|
||||
color: Colors.grey,
|
||||
color: context.color.onSurfaceVariant,
|
||||
size: 12,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -37,20 +37,16 @@ class _MyButtonState extends State<MyButton> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEnabled = widget.onPressed != null || widget.onLongPress != null;
|
||||
final isDark = isDarkMode(context);
|
||||
final disabledBgColor = isDark
|
||||
? const Color(0xFF353535)
|
||||
: const Color(0xFFE0E0E0);
|
||||
final disabledFgColor = isDark
|
||||
? const Color(0xFF757575)
|
||||
: const Color(0xFF9E9E9E);
|
||||
final colors = context.color;
|
||||
final disabledBgColor = colors.surfaceContainerHighest;
|
||||
final disabledFgColor = colors.onSurface.withValues(alpha: 0.38);
|
||||
|
||||
late final ButtonStyle buttonStyle;
|
||||
switch (widget.variant) {
|
||||
case MyButtonVariant.primary:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
foregroundColor: colors.onPrimary,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
minimumSize: const Size.fromHeight(60),
|
||||
|
|
@ -65,8 +61,8 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.secondary:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
|
||||
foregroundColor: isDark ? Colors.white : Colors.black87,
|
||||
backgroundColor: colors.surfaceContainerHigh,
|
||||
foregroundColor: colors.onSurface,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
minimumSize: const Size.fromHeight(60),
|
||||
|
|
@ -82,9 +78,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
case MyButtonVariant.text:
|
||||
buttonStyle = TextButton.styleFrom(
|
||||
minimumSize: const Size(0, 50),
|
||||
foregroundColor: isDark
|
||||
? Colors.white.withValues(alpha: 0.7)
|
||||
: Colors.black.withValues(alpha: 0.7),
|
||||
foregroundColor: colors.onSurfaceVariant,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
|
|
@ -97,7 +91,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
case MyButtonVariant.primaryMiddle:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
foregroundColor: colors.onPrimary,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
minimumSize: const Size(0, 48),
|
||||
|
|
@ -116,7 +110,7 @@ class _MyButtonState extends State<MyButton> {
|
|||
case MyButtonVariant.primaryDense:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
foregroundColor: colors.onPrimary,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
minimumSize: const Size(0, 40),
|
||||
|
|
@ -134,8 +128,8 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.secondaryDense:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
|
||||
foregroundColor: isDark ? Colors.white : Colors.black87,
|
||||
backgroundColor: colors.surfaceContainerHigh,
|
||||
foregroundColor: colors.onSurface,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
minimumSize: const Size(0, 40),
|
||||
|
|
@ -153,8 +147,8 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.secondaryMiddle:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
|
||||
foregroundColor: isDark ? Colors.white : Colors.black87,
|
||||
backgroundColor: colors.surfaceContainerHigh,
|
||||
foregroundColor: colors.onSurface,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
minimumSize: const Size(0, 48),
|
||||
|
|
@ -172,8 +166,8 @@ class _MyButtonState extends State<MyButton> {
|
|||
);
|
||||
case MyButtonVariant.secondaryTiny:
|
||||
buttonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200],
|
||||
foregroundColor: isDark ? Colors.white : Colors.black87,
|
||||
backgroundColor: colors.surfaceContainerHigh,
|
||||
foregroundColor: colors.onSurface,
|
||||
disabledBackgroundColor: disabledBgColor,
|
||||
disabledForegroundColor: disabledFgColor,
|
||||
minimumSize: const Size(40, 23),
|
||||
|
|
|
|||
|
|
@ -22,17 +22,9 @@ class MyChip extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = isDarkMode(context);
|
||||
final background = selected
|
||||
? context.color.primary
|
||||
: isDark
|
||||
? Colors.grey[800]!
|
||||
: Colors.grey[200]!;
|
||||
final foreground = selected
|
||||
? Colors.black87
|
||||
: isDark
|
||||
? Colors.white
|
||||
: Colors.black87;
|
||||
final colors = context.color;
|
||||
final background = selected ? colors.primary : colors.surfaceContainerHigh;
|
||||
final foreground = selected ? colors.onPrimary : colors.onSurface;
|
||||
Widget chip = AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
height: 36,
|
||||
|
|
|
|||
|
|
@ -29,23 +29,19 @@ class _MyIconButtonState extends State<MyIconButton> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEnabled = widget.onPressed != null || widget.onLongPress != null;
|
||||
final isDark = isDarkMode(context);
|
||||
final disabledBgColor = isDark
|
||||
? const Color(0xFF353535)
|
||||
: const Color(0xFFE0E0E0);
|
||||
final disabledFgColor = isDark
|
||||
? const Color(0xFF757575)
|
||||
: const Color(0xFF9E9E9E);
|
||||
final colors = context.color;
|
||||
final disabledBgColor = colors.surfaceContainerHighest;
|
||||
final disabledFgColor = colors.onSurface.withValues(alpha: 0.38);
|
||||
|
||||
late final Color bgColor;
|
||||
late final Color fgColor;
|
||||
|
||||
if (widget.variant == MyIconButtonVariant.primary) {
|
||||
bgColor = context.color.primary;
|
||||
fgColor = Colors.black87;
|
||||
fgColor = colors.onPrimary;
|
||||
} else {
|
||||
bgColor = isDark ? Colors.grey[800]! : Colors.grey[200]!;
|
||||
fgColor = isDark ? Colors.white : Colors.black87;
|
||||
bgColor = colors.surfaceContainerHigh;
|
||||
fgColor = colors.onSurface;
|
||||
}
|
||||
|
||||
final childButton = FilledButton(
|
||||
|
|
|
|||
|
|
@ -45,23 +45,11 @@ class MyInput extends StatefulWidget {
|
|||
class _MyInputState extends State<MyInput> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = isDarkMode(context);
|
||||
|
||||
final inputFillColor = isDark
|
||||
? Colors.white.withValues(alpha: 0.08)
|
||||
: Colors.black.withValues(alpha: 0.05);
|
||||
|
||||
final inputBorderColor = isDark
|
||||
? Colors.white.withValues(alpha: 0.05)
|
||||
: Colors.black.withValues(alpha: 0.05);
|
||||
|
||||
final inputHintColor = isDark
|
||||
? Colors.white.withValues(alpha: 0.5)
|
||||
: Colors.black.withValues(alpha: 0.5);
|
||||
|
||||
final prefixIconColor = isDark
|
||||
? Colors.white.withValues(alpha: 0.6)
|
||||
: Colors.black.withValues(alpha: 0.6);
|
||||
final colors = context.color;
|
||||
final inputFillColor = colors.surfaceContainerLow;
|
||||
final inputBorderColor = colors.outlineVariant.withValues(alpha: 0.6);
|
||||
final inputHintColor = colors.onSurfaceVariant.withValues(alpha: 0.75);
|
||||
final prefixIconColor = colors.onSurfaceVariant;
|
||||
|
||||
return ReactiveTapFeedback(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
|
|
@ -109,7 +97,7 @@ class _MyInputState extends State<MyInput> {
|
|||
style: TextStyle(
|
||||
fontSize: widget.dense ? 16 : 18,
|
||||
fontWeight: widget.fontWeight ?? FontWeight.w500,
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
color: colors.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
isDense: widget.dense,
|
||||
|
|
@ -139,25 +127,25 @@ class _MyInputState extends State<MyInput> {
|
|||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(widget.dense ? 12 : 18),
|
||||
borderSide: BorderSide(
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
color: colors.onSurface,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(widget.dense ? 12 : 18),
|
||||
borderSide: const BorderSide(
|
||||
color: Colors.redAccent,
|
||||
borderSide: BorderSide(
|
||||
color: colors.error,
|
||||
),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(widget.dense ? 12 : 18),
|
||||
borderSide: const BorderSide(
|
||||
color: Colors.redAccent,
|
||||
borderSide: BorderSide(
|
||||
color: colors.error,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
errorStyle: const TextStyle(
|
||||
color: Colors.redAccent,
|
||||
errorStyle: TextStyle(
|
||||
color: colors.error,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
|
|
@ -173,7 +161,7 @@ class _MyInputState extends State<MyInput> {
|
|||
suffixIcon: widget.suffixIcon != null
|
||||
? IconTheme(
|
||||
data: IconThemeData(
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
color: colors.onSurface,
|
||||
),
|
||||
child: widget.suffixIcon!,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,23 +10,23 @@ class ProBadge extends StatelessWidget {
|
|||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.shade700,
|
||||
color: context.appColor(AppColor.premium),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.star,
|
||||
size: 10,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.onWarning),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
context.lang.backupCloudProBadge,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.onWarning),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoPlayerHelper extends StatefulWidget {
|
||||
|
|
@ -98,15 +99,15 @@ class _VideoPlayerHelperState extends State<VideoPlayerHelper>
|
|||
child: Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black54,
|
||||
decoration: BoxDecoration(
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
_isPaused
|
||||
? Icons.pause_rounded
|
||||
: Icons.play_arrow_rounded,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 36,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,41 +1,97 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class DefaultColors {
|
||||
static const messageSelf = Color.fromARGB(255, 58, 136, 102);
|
||||
static const messageOther = Color.fromARGB(233, 68, 137, 255);
|
||||
/// Every Twonly-specific color, with its light and dark values side by side.
|
||||
///
|
||||
/// To add a color, add exactly one entry here and use it with:
|
||||
/// `context.appColor(AppColor.yourColor)`.
|
||||
enum AppColor {
|
||||
// Chat bubbles
|
||||
chatBubbleSent(light: Color(0xFFE8EBE9), dark: Color(0xFF292C2A)),
|
||||
chatBubbleReceived(light: Color(0xFFF3F4F3), dark: Color(0xFF1D201E)),
|
||||
onChatBubbleSent(light: Color(0xFF191C1A), dark: Color(0xFFF0F2F0)),
|
||||
onChatBubbleReceived(light: Color(0xFF191C1A), dark: Color(0xFFE2E4E1)),
|
||||
chatBubbleDeleted(light: Color(0xFFE1E4E1), dark: Color(0xFF101311)),
|
||||
onChatBubbleDeleted(light: Color(0xFF626863), dark: Color(0xFF8A918B)),
|
||||
chatMessageMeta(light: Color(0xFF69706A), dark: Color(0xFFAAB2AC)),
|
||||
reactionBackground(light: Color(0xFFE1E5E2), dark: Color(0xFF343936)),
|
||||
onReaction(light: Color(0xFF191C1A), dark: Color(0xFFF0F2F0)),
|
||||
|
||||
// Feedback and status
|
||||
success(light: Color(0xFF177245), dark: Color(0xFF65D99A)),
|
||||
onSuccess(light: Color(0xFFFFFFFF), dark: Color(0xFF00391F)),
|
||||
successContainer(light: Color(0xFFC0F0D2), dark: Color(0xFF00522F)),
|
||||
onSuccessContainer(light: Color(0xFF002112), dark: Color(0xFF83F7B5)),
|
||||
warning(light: Color(0xFF8A5700), dark: Color(0xFFFFB95F)),
|
||||
onWarning(light: Color(0xFFFFFFFF), dark: Color(0xFF492900)),
|
||||
warningContainer(light: Color(0xFFFFDDB4), dark: Color(0xFF673D00)),
|
||||
onWarningContainer(light: Color(0xFF2C1700), dark: Color(0xFFFFDDB4)),
|
||||
info(light: Color(0xFF35618D), dark: Color(0xFF9FCBFF)),
|
||||
onInfo(light: Color(0xFFFFFFFF), dark: Color(0xFF003257)),
|
||||
infoContainer(light: Color(0xFFD1E4FF), dark: Color(0xFF164A70)),
|
||||
onInfoContainer(light: Color(0xFF001D35), dark: Color(0xFFD1E4FF)),
|
||||
premium(light: Color(0xFF8A6500), dark: Color(0xFFFFC94C)),
|
||||
verificationPending(light: Color(0xFF007FA3), dark: Color(0xFF5DD5F5)),
|
||||
verificationIdle(light: Color(0xFF7A8497), dark: Color(0xFFAEB8CC)),
|
||||
|
||||
// QR codes
|
||||
qrForeground(light: Color(0xFF000000), dark: Color(0xFF000000)),
|
||||
qrBackground(light: Color(0xFFFFFFFF), dark: Color(0xFFFFFFFF)),
|
||||
|
||||
// Controls displayed over photos and videos
|
||||
mediaForeground(light: Color(0xFFFFFFFF), dark: Color(0xFFFFFFFF)),
|
||||
mediaForegroundMuted(light: Color(0xB3FFFFFF), dark: Color(0xB3FFFFFF)),
|
||||
mediaBackground(light: Color(0xFF000000), dark: Color(0xFF000000)),
|
||||
mediaScrim(light: Color(0x8A000000), dark: Color(0x8A000000)),
|
||||
recording(light: Color(0xFFD32F2F), dark: Color(0xFFFF5252)),
|
||||
scanHighlight(light: Color(0xFF76FF03), dark: Color(0xFF76FF03)),
|
||||
|
||||
// Message-kind indicators
|
||||
messageText(light: Color(0xFF35618D), dark: Color(0xFF9FCBFF)),
|
||||
messageRestore(light: Color(0xFFB85F00), dark: Color(0xFFFFB95F)),
|
||||
messageWidget(light: Color(0xFF79528D), dark: Color(0xFFD7B9E8)),
|
||||
messageVideo(light: Color(0xFF9C3A8D), dark: Color(0xFFFFA9E5)),
|
||||
messageAudio(light: Color(0xFFB85F00), dark: Color(0xFFFFB68A)),
|
||||
messageImage(light: Color(0xFFBA1A1A), dark: Color(0xFFFFB4AB));
|
||||
|
||||
const AppColor({required this.light, required this.dark});
|
||||
|
||||
final Color light;
|
||||
final Color dark;
|
||||
|
||||
Color resolve(Brightness brightness) {
|
||||
return brightness == Brightness.dark ? dark : light;
|
||||
}
|
||||
}
|
||||
|
||||
Color getMessageColorFromType(
|
||||
Message message,
|
||||
MediaFile? mediaFile,
|
||||
BuildContext context,
|
||||
) {
|
||||
Color color;
|
||||
/// Resolves [AppColor] tokens for one theme and keeps theme transitions smooth.
|
||||
@immutable
|
||||
class AppColors extends ThemeExtension<AppColors> {
|
||||
const AppColors.light()
|
||||
: _brightness = Brightness.light,
|
||||
_interpolatedColors = null;
|
||||
|
||||
if (message.type == MessageType.restoreFlameCounter.name) {
|
||||
color = Colors.orange;
|
||||
} else if (message.type == MessageType.text.name) {
|
||||
color = Colors.blueAccent;
|
||||
} else if (message.isWidgetMedia) {
|
||||
color = const Color.fromARGB(255, 155, 89, 182);
|
||||
} else if (mediaFile != null) {
|
||||
if (mediaFile.requiresAuthentication) {
|
||||
color = context.color.primary;
|
||||
} else {
|
||||
if (mediaFile.type == MediaType.video) {
|
||||
color = const Color.fromARGB(255, 243, 33, 208);
|
||||
} else if (mediaFile.type == MediaType.audio) {
|
||||
color = const Color.fromARGB(255, 252, 149, 85);
|
||||
} else {
|
||||
color = Colors.redAccent;
|
||||
const AppColors.dark()
|
||||
: _brightness = Brightness.dark,
|
||||
_interpolatedColors = null;
|
||||
|
||||
const AppColors._interpolated(this._interpolatedColors) : _brightness = null;
|
||||
|
||||
final Brightness? _brightness;
|
||||
final Map<AppColor, Color>? _interpolatedColors;
|
||||
|
||||
Color operator [](AppColor color) {
|
||||
return _interpolatedColors?[color] ?? color.resolve(_brightness!);
|
||||
}
|
||||
|
||||
@override
|
||||
AppColors copyWith() => this;
|
||||
|
||||
@override
|
||||
AppColors lerp(covariant AppColors? other, double t) {
|
||||
if (other == null) return this;
|
||||
return AppColors._interpolated({
|
||||
for (final color in AppColor.values)
|
||||
color: Color.lerp(this[color], other[color], t)!,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return (isDarkMode(context)) ? Colors.white : Colors.black;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,55 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/visual/themes/colors.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) {
|
||||
final base = ThemeData.dark().copyWith(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
Color _onPrimary(Color primary) {
|
||||
return ThemeData.estimateBrightnessForColor(primary) == Brightness.dark
|
||||
? const Color(0xFFFFFFFF)
|
||||
: const Color(0xFF082117);
|
||||
}
|
||||
|
||||
ColorScheme _darkColorScheme(Color primary) {
|
||||
return ColorScheme.fromSeed(
|
||||
brightness: Brightness.dark,
|
||||
seedColor: primary,
|
||||
primary: primary,
|
||||
surface: const Color.fromARGB(255, 20, 18, 23),
|
||||
surfaceContainer: const Color.fromARGB(255, 45, 41, 54),
|
||||
surfaceContainerLow: const Color.fromARGB(255, 38, 34, 45),
|
||||
surfaceContainerHigh: const Color.fromARGB(255, 52, 48, 62),
|
||||
),
|
||||
).copyWith(
|
||||
onPrimary: _onPrimary(primary),
|
||||
surface: const Color(0xFF111412),
|
||||
onSurface: const Color(0xFFE2E4E1),
|
||||
onSurfaceVariant: const Color(0xFFBEC9C0),
|
||||
surfaceDim: const Color(0xFF111412),
|
||||
surfaceBright: const Color(0xFF373A37),
|
||||
surfaceContainerLowest: const Color(0xFF0B0E0C),
|
||||
surfaceContainerLow: const Color(0xFF171A18),
|
||||
surfaceContainer: const Color(0xFF1D211E),
|
||||
surfaceContainerHigh: const Color(0xFF282C29),
|
||||
surfaceContainerHighest: const Color(0xFF333734),
|
||||
outline: const Color(0xFF89938B),
|
||||
outlineVariant: const Color(0xFF404842),
|
||||
inverseSurface: const Color(0xFFE2E4E1),
|
||||
onInverseSurface: const Color(0xFF2E312F),
|
||||
shadow: const Color(0xFF000000),
|
||||
scrim: const Color(0xFF000000),
|
||||
error: const Color(0xFFFFB4AB),
|
||||
onError: const Color(0xFF690005),
|
||||
errorContainer: const Color(0xFF93000A),
|
||||
onErrorContainer: const Color(0xFFFFDAD6),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) {
|
||||
final colorScheme = _darkColorScheme(primary);
|
||||
final base = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: colorScheme.surface,
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
extensions: const [AppColors.dark()],
|
||||
);
|
||||
return base.copyWith(
|
||||
textTheme: base.textTheme.apply(
|
||||
|
|
|
|||
|
|
@ -1,20 +1,56 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/themes/colors.dart';
|
||||
|
||||
const defaultPrimaryColor = Color(0xFF32be80);
|
||||
const defaultPrimaryColor = Color(0xFF32BE80);
|
||||
|
||||
ThemeData getLightTheme([Color primary = defaultPrimaryColor]) {
|
||||
final base = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
Color _onPrimary(Color primary) {
|
||||
return ThemeData.estimateBrightnessForColor(primary) == Brightness.dark
|
||||
? const Color(0xFFFFFFFF)
|
||||
: const Color(0xFF082117);
|
||||
}
|
||||
|
||||
/// Material colors for the light theme. Twonly-specific light/dark pairs live
|
||||
/// together in `colors.dart`.
|
||||
ColorScheme _lightColorScheme(Color primary) {
|
||||
return ColorScheme.fromSeed(
|
||||
seedColor: primary,
|
||||
primary: primary,
|
||||
surface: Colors.white,
|
||||
),
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
).copyWith(
|
||||
onPrimary: _onPrimary(primary),
|
||||
surface: const Color(0xFFFFFFFF),
|
||||
onSurface: const Color(0xFF191C1A),
|
||||
onSurfaceVariant: const Color(0xFF414943),
|
||||
surfaceDim: const Color(0xFFD9DCD9),
|
||||
surfaceBright: const Color(0xFFFFFFFF),
|
||||
surfaceContainerLowest: const Color(0xFFFFFFFF),
|
||||
surfaceContainerLow: const Color(0xFFF5F7F5),
|
||||
surfaceContainer: const Color(0xFFEFF2EF),
|
||||
surfaceContainerHigh: const Color(0xFFE9ECE9),
|
||||
surfaceContainerHighest: const Color(0xFFE3E7E3),
|
||||
outline: const Color(0xFF717971),
|
||||
outlineVariant: const Color(0xFFC1C9C1),
|
||||
inverseSurface: const Color(0xFF2E312F),
|
||||
onInverseSurface: const Color(0xFFF0F2F0),
|
||||
shadow: const Color(0xFF000000),
|
||||
scrim: const Color(0xFF000000),
|
||||
error: const Color(0xFFBA1A1A),
|
||||
onError: const Color(0xFFFFFFFF),
|
||||
errorContainer: const Color(0xFFFFDAD6),
|
||||
onErrorContainer: const Color(0xFF410002),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData getLightTheme([Color primary = defaultPrimaryColor]) {
|
||||
final colorScheme = _lightColorScheme(primary);
|
||||
final base = ThemeData(
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: colorScheme.surface,
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
extensions: const [AppColors.light()],
|
||||
);
|
||||
return base.copyWith(
|
||||
textTheme: base.textTheme.apply(
|
||||
|
|
@ -28,20 +64,17 @@ final ThemeData lightTheme = getLightTheme();
|
|||
|
||||
final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom(
|
||||
backgroundColor: defaultPrimaryColor,
|
||||
foregroundColor: Colors.black87,
|
||||
foregroundColor: const Color(0xFF082117),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
);
|
||||
|
||||
ButtonStyle secondaryGreyButtonStyle(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return FilledButton.styleFrom(
|
||||
backgroundColor: isDarkMode(context) ? Colors.grey[800] : Colors.grey[200],
|
||||
foregroundColor: isDarkMode(context) ? Colors.white : Colors.black87,
|
||||
backgroundColor: colors.surfaceContainerHigh,
|
||||
foregroundColor: colors.onSurface,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart';
|
||||
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||
|
|
@ -96,7 +97,9 @@ class MainCameraPreview extends StatelessWidget {
|
|||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white.withAlpha(150),
|
||||
color: context.appColor(
|
||||
AppColor.mediaForegroundMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_components/zoom_tutorial_overlay.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||
|
|
@ -66,13 +67,13 @@ class CameraBottomControls extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (!isVideoRecording)
|
||||
_buildSideButtonLeft()
|
||||
_buildSideButtonLeft(context)
|
||||
else
|
||||
_buildLockOrStopButton(),
|
||||
_buildShutterButton(),
|
||||
_buildLockOrStopButton(context),
|
||||
_buildShutterButton(context),
|
||||
if (!isVideoRecording)
|
||||
if (isFront)
|
||||
_buildSideButtonRight()
|
||||
_buildSideButtonRight(context)
|
||||
else
|
||||
const SizedBox(width: 80)
|
||||
else
|
||||
|
|
@ -85,7 +86,7 @@ class CameraBottomControls extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildLockOrStopButton() {
|
||||
Widget _buildLockOrStopButton(BuildContext context) {
|
||||
if (videoRecordingLocked) {
|
||||
// Show stop button
|
||||
return GestureDetector(
|
||||
|
|
@ -100,7 +101,7 @@ class CameraBottomControls extends StatelessWidget {
|
|||
width: 34,
|
||||
height: 34,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
color: context.appColor(AppColor.recording),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
|
|
@ -113,7 +114,7 @@ class CameraBottomControls extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
Widget _buildSideButtonLeft() {
|
||||
Widget _buildSideButtonLeft(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onPressSideButtonLeft,
|
||||
child: Align(
|
||||
|
|
@ -128,7 +129,7 @@ class CameraBottomControls extends StatelessWidget {
|
|||
? FontAwesomeIcons.xmark
|
||||
: FontAwesomeIcons.arrowLeft
|
||||
: FontAwesomeIcons.photoFilm,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
|
|
@ -137,7 +138,7 @@ class CameraBottomControls extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildShutterButton() {
|
||||
Widget _buildShutterButton(BuildContext context) {
|
||||
return StreamBuilder(
|
||||
stream: userService.onUserUpdated,
|
||||
builder: (context, snapshot) {
|
||||
|
|
@ -156,7 +157,9 @@ class CameraBottomControls extends StatelessWidget {
|
|||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
width: 7,
|
||||
color: isVideoRecording ? Colors.red : Colors.white,
|
||||
color: isVideoRecording
|
||||
? context.appColor(AppColor.recording)
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
child: mc.currentFilterType.preview,
|
||||
|
|
@ -168,7 +171,7 @@ class CameraBottomControls extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildSideButtonRight() {
|
||||
Widget _buildSideButtonRight(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onPressSideButtonRight,
|
||||
child: Align(
|
||||
|
|
@ -184,7 +187,7 @@ class CameraBottomControls extends StatelessWidget {
|
|||
? FontAwesomeIcons.xmark
|
||||
: FontAwesomeIcons.arrowRight
|
||||
: FontAwesomeIcons.faceGrinTongueSquint,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
|
|
@ -249,10 +252,10 @@ class _AnimatedLockButtonState extends State<_AnimatedLockButton>
|
|||
height: 50,
|
||||
width: 80,
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: const Center(
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.lock,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class CameraScannedOverlay extends StatelessWidget {
|
|||
color: Colors.transparent,
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.shareFromSquare,
|
||||
color: isDarkMode(context) ? Colors.white : Colors.black,
|
||||
color: context.color.onSurface,
|
||||
size: 17,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
/// Full-screen white overlay used as a "flash" when taking selfies
|
||||
/// with the front camera and flash enabled.
|
||||
|
|
@ -11,7 +12,7 @@ class CameraSelfieFlash extends StatelessWidget {
|
|||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@ class CameraTopActions extends StatelessWidget {
|
|||
: Icons.flash_off_rounded,
|
||||
tooltipText: context.lang.toggleFlashLight,
|
||||
color: selectedCameraDetails.isFlashOn
|
||||
? Colors.white
|
||||
: Colors.white.withAlpha(160),
|
||||
? context.appColor(AppColor.mediaForeground)
|
||||
: context.appColor(AppColor.mediaForegroundMuted),
|
||||
onPressed: onToggleFlash,
|
||||
),
|
||||
if (!hasAudioPermission)
|
||||
ActionButton(
|
||||
Icons.mic_off_rounded,
|
||||
color: Colors.white.withAlpha(160),
|
||||
color: context.appColor(AppColor.mediaForegroundMuted),
|
||||
tooltipText: 'Allow microphone access for video recording.',
|
||||
onPressed: onRequestMicrophone,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -105,13 +105,13 @@ class _ZoomTutorialOverlayState extends State<ZoomTutorialOverlay>
|
|||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
color: context.appColor(AppColor.mediaBackground),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
context.lang.dragToZoom,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.3,
|
||||
|
|
@ -131,13 +131,15 @@ class _ZoomTutorialOverlayState extends State<ZoomTutorialOverlay>
|
|||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
color: context.appColor(
|
||||
AppColor.mediaForegroundMuted,
|
||||
),
|
||||
child: const Center(
|
||||
),
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.handPointer,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ class _CameraPreviewControllerViewState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
widget.mainController.scanHighlightColor = context.appColor(
|
||||
AppColor.scanHighlight,
|
||||
);
|
||||
if (AppState.hasCameraPermissions) {
|
||||
return CameraPreviewView(
|
||||
sendToGroup: widget.sendToGroup,
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ class MainCameraController {
|
|||
bool _isBusy = false;
|
||||
bool _isBusyFaces = false;
|
||||
CustomPaint? qrCodePain;
|
||||
Color? scanHighlightColor;
|
||||
CustomPaint? facePaint;
|
||||
Offset? focusPointOffset;
|
||||
|
||||
|
|
@ -529,12 +530,14 @@ class MainCameraController {
|
|||
final controller = cameraController;
|
||||
if (inputImage.metadata?.size != null &&
|
||||
inputImage.metadata?.rotation != null &&
|
||||
controller != null) {
|
||||
controller != null &&
|
||||
scanHighlightColor != null) {
|
||||
final painter = BarcodeDetectorPainter(
|
||||
barcodes,
|
||||
inputImage.metadata!.size,
|
||||
inputImage.metadata!.rotation,
|
||||
controller.description.lensDirection,
|
||||
scanHighlightColor!,
|
||||
);
|
||||
qrCodePain = CustomPaint(painter: painter);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,19 +10,21 @@ class BarcodeDetectorPainter extends CustomPainter {
|
|||
this.imageSize,
|
||||
this.rotation,
|
||||
this.cameraLensDirection,
|
||||
this.color,
|
||||
);
|
||||
|
||||
final List<Barcode> barcodes;
|
||||
final Size imageSize;
|
||||
final InputImageRotation rotation;
|
||||
final CameraLensDirection cameraLensDirection;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.0
|
||||
..color = Colors.lightGreenAccent;
|
||||
..color = color;
|
||||
|
||||
for (final barcode in barcodes) {
|
||||
final cornerPoints = <Offset>[];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
abstract class FaceFilterPainter extends CustomPainter {
|
||||
FaceFilterPainter(
|
||||
|
|
@ -34,7 +35,7 @@ class Preview extends StatelessWidget {
|
|||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.withValues(alpha: 0.2),
|
||||
color: context.color.surfaceContainerHighest,
|
||||
),
|
||||
child: Center(
|
||||
child: child,
|
||||
|
|
|
|||
|
|
@ -89,12 +89,14 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_imageSaving || widget.isLoading)
|
||||
const SizedBox(
|
||||
SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 1,
|
||||
valueColor: AlwaysStoppedAnimation(Colors.white),
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (_imageSaved)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class ShowTitleText extends StatelessWidget {
|
||||
const ShowTitleText({
|
||||
|
|
@ -15,13 +16,13 @@ class ShowTitleText extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: isLink ? 14 : 24,
|
||||
decoration: TextDecoration.none,
|
||||
shadows: const [
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Color.fromARGB(122, 0, 0, 0),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class VideoRecordingTimer extends StatelessWidget {
|
||||
const VideoRecordingTimer({
|
||||
|
|
@ -38,19 +39,23 @@ class VideoRecordingTimer extends StatelessWidget {
|
|||
maxRecordingTime.inMilliseconds)
|
||||
.clamp(0.0, 1.0),
|
||||
strokeWidth: 4,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(Colors.red),
|
||||
backgroundColor: Colors.grey[300],
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
context.appColor(AppColor.recording),
|
||||
),
|
||||
backgroundColor: context.appColor(
|
||||
AppColor.mediaForegroundMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
elapsed.inSeconds.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 17,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Color.fromARGB(122, 0, 0, 0),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'dart:math';
|
|||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||
|
||||
String beautifulZoomScale(double scale) {
|
||||
|
|
@ -60,7 +61,7 @@ class CameraZoomButtons extends StatelessWidget {
|
|||
|
||||
final zoomButtonStyle = TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
foregroundColor: Colors.white,
|
||||
foregroundColor: context.appColor(AppColor.mediaForeground),
|
||||
minimumSize: const Size(40, 40),
|
||||
alignment: Alignment.center,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
|
|
@ -90,7 +91,7 @@ class CameraZoomButtons extends StatelessWidget {
|
|||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
child: ColoredBox(
|
||||
color: const Color.fromARGB(90, 0, 0, 0),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
|
@ -98,7 +99,9 @@ class CameraZoomButtons extends StatelessWidget {
|
|||
TextButton(
|
||||
style: zoomButtonStyle.copyWith(
|
||||
foregroundColor: WidgetStateProperty.all(
|
||||
isSmallerFocused ? Colors.yellow : Colors.white,
|
||||
isSmallerFocused
|
||||
? context.appColor(AppColor.warning)
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
|
|
@ -121,7 +124,9 @@ class CameraZoomButtons extends StatelessWidget {
|
|||
TextButton(
|
||||
style: zoomButtonStyle.copyWith(
|
||||
foregroundColor: WidgetStateProperty.all(
|
||||
isMiddleFocused ? Colors.yellow : Colors.white,
|
||||
isMiddleFocused
|
||||
? context.appColor(AppColor.warning)
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
|
|
@ -142,7 +147,9 @@ class CameraZoomButtons extends StatelessWidget {
|
|||
TextButton(
|
||||
style: zoomButtonStyle.copyWith(
|
||||
foregroundColor: WidgetStateProperty.all(
|
||||
(scaleFactor >= 2) ? Colors.yellow : Colors.white,
|
||||
(scaleFactor >= 2)
|
||||
? context.appColor(AppColor.warning)
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
|
|
@ -96,8 +96,8 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
|
|||
getContactDisplayName(widget.contact!),
|
||||
)
|
||||
: context.lang.qrScannerVerifyHint,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
|
|
|
|||
|
|
@ -429,13 +429,13 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!mediaStoreFutureReady || sendingImage)
|
||||
const SizedBox(
|
||||
SizedBox(
|
||||
height: 14,
|
||||
width: 14,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Colors.black87,
|
||||
context.color.onPrimary,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,10 +49,10 @@ class BestFriendsSelector extends StatelessWidget {
|
|||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.outline.withAlpha(50),
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 10.9,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||
color: context.color.shadow.withValues(alpha: 0.1),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
|
|
@ -135,10 +135,10 @@ class UserCheckbox extends StatelessWidget {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.outline.withAlpha(50),
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 10.9,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||
color: context.color.shadow.withValues(alpha: 0.1),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class _SelectShowTimeState extends State<SelectShowTime> {
|
|||
height: 350,
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
margin: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
color: CupertinoColors.systemBackground.resolveFrom(context),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
|
|
@ -37,7 +37,7 @@ class _SelectShowTimeState extends State<SelectShowTime> {
|
|||
margin: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
height: 3,
|
||||
width: 60,
|
||||
|
|
|
|||
|
|
@ -545,9 +545,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
await askToCloseThenClose();
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: widget.sharedFromGallery
|
||||
? null
|
||||
: Colors.white.withAlpha(0),
|
||||
backgroundColor: widget.sharedFromGallery ? null : Colors.transparent,
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Stack(
|
||||
key: _editorStackKey,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class ActionButton extends StatelessWidget {
|
||||
const ActionButton(
|
||||
|
|
@ -27,11 +28,11 @@ class ActionButton extends StatelessWidget {
|
|||
icon as FaIconData?,
|
||||
size: 25,
|
||||
color: disable
|
||||
? const Color.fromARGB(154, 255, 255, 255)
|
||||
: color ?? Colors.white,
|
||||
shadows: const [
|
||||
? context.appColor(AppColor.mediaForegroundMuted)
|
||||
: color ?? context.appColor(AppColor.mediaForeground),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Color.fromARGB(122, 0, 0, 0),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
|
|
@ -40,11 +41,11 @@ class ActionButton extends StatelessWidget {
|
|||
icon as IconData?,
|
||||
size: 30,
|
||||
color: disable
|
||||
? const Color.fromARGB(154, 255, 255, 255)
|
||||
: color ?? Colors.white,
|
||||
shadows: const [
|
||||
? context.appColor(AppColor.mediaForegroundMuted)
|
||||
: color ?? context.appColor(AppColor.mediaForeground),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Color.fromARGB(122, 0, 0, 0),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ Future<void> showDisplayTimePicker(
|
|||
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (sheetContext) {
|
||||
return SelectShowTime(
|
||||
initialItem: initialItem,
|
||||
|
|
|
|||
|
|
@ -65,12 +65,14 @@ class EditorBottomBar extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isSending)
|
||||
const SizedBox(
|
||||
SizedBox(
|
||||
height: 12,
|
||||
width: 12,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation(Colors.black87),
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
context.color.onPrimary,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class EditorSideToolbar extends StatelessWidget {
|
|||
onPressed: () async {
|
||||
final layer = await showModalBottomSheet<Layer>(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: context.color.surface,
|
||||
builder: (context) => const EmojiPickerBottom(),
|
||||
);
|
||||
if (layer == null) return;
|
||||
|
|
@ -124,7 +124,7 @@ class EditorSideToolbar extends StatelessWidget {
|
|||
tooltipText: context.lang.shareImageSendToWidget,
|
||||
color: sendToWidget || highlightWidgetOption
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.white,
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
disable: isUpdatingWidgetMode,
|
||||
onPressed: onToggleSendToWidget,
|
||||
),
|
||||
|
|
@ -148,7 +148,7 @@ class EditorSideToolbar extends StatelessWidget {
|
|||
tooltipText: 'Trim video',
|
||||
color: trimmerVisible
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.white,
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
onPressed: onToggleTrimmer,
|
||||
),
|
||||
],
|
||||
|
|
@ -160,8 +160,8 @@ class EditorSideToolbar extends StatelessWidget {
|
|||
: Icons.volume_up_rounded,
|
||||
tooltipText: 'Enable Audio in Video',
|
||||
color: (mediaService.removeAudio)
|
||||
? Colors.white.withAlpha(160)
|
||||
: Colors.white,
|
||||
? context.appColor(AppColor.mediaForegroundMuted)
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
onPressed: onToggleAudio,
|
||||
),
|
||||
],
|
||||
|
|
@ -170,7 +170,7 @@ class EditorSideToolbar extends StatelessWidget {
|
|||
ActionButton(
|
||||
Icons.crop_rotate_outlined,
|
||||
tooltipText: 'Crop or rotate image',
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
onPressed: () {
|
||||
layerStack.toggleBackgroundEditing();
|
||||
onChanged();
|
||||
|
|
@ -184,7 +184,7 @@ class EditorSideToolbar extends StatelessWidget {
|
|||
tooltipText: context.lang.protectAsARealTwonly,
|
||||
color: media.requiresAuthentication
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.white,
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
onPressed: onToggleRequiresAuth,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ class _DrawLayerState extends State<DrawLayer> {
|
|||
FontAwesomeIcons.arrowRotateLeft,
|
||||
tooltipText: context.lang.undo,
|
||||
color: widget.layerData.control.paths.isNotEmpty
|
||||
? Colors.white
|
||||
: Colors.white.withAlpha(80),
|
||||
? context.appColor(AppColor.mediaForeground)
|
||||
: context.appColor(AppColor.mediaForegroundMuted),
|
||||
onPressed: () {
|
||||
if (widget.layerData.control.paths.isEmpty) return;
|
||||
skipNextEvent = true;
|
||||
|
|
@ -123,8 +123,8 @@ class _DrawLayerState extends State<DrawLayer> {
|
|||
tooltipText: context.lang.redo,
|
||||
FontAwesomeIcons.arrowRotateRight,
|
||||
color: undoList.isNotEmpty
|
||||
? Colors.white
|
||||
: Colors.white.withAlpha(80),
|
||||
? context.appColor(AppColor.mediaForeground)
|
||||
: context.appColor(AppColor.mediaForegroundMuted),
|
||||
onPressed: () {
|
||||
if (undoList.isEmpty) return;
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ class MagnifyingGlass extends StatelessWidget {
|
|||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
|
||||
|
||||
|
|
@ -171,7 +172,9 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
|||
child: ActionButton(
|
||||
FontAwesomeIcons.trashCan,
|
||||
tooltipText: '',
|
||||
color: deleteLayer ? Colors.red : Colors.white,
|
||||
color: deleteLayer
|
||||
? context.appColor(AppColor.recording)
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/datetime_filter.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/image_filter.dart';
|
||||
|
|
@ -45,11 +46,11 @@ class FilterText extends StatelessWidget {
|
|||
this.text, {
|
||||
super.key,
|
||||
this.fontSize = 24,
|
||||
this.color = Colors.white,
|
||||
this.color,
|
||||
});
|
||||
final String text;
|
||||
final double fontSize;
|
||||
final Color color;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -58,10 +59,10 @@ class FilterText extends StatelessWidget {
|
|||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
color: color,
|
||||
shadows: const [
|
||||
color: color ?? context.appColor(AppColor.mediaForeground),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Color.fromARGB(122, 0, 0, 0),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import 'package:intl/intl.dart';
|
|||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart';
|
||||
|
||||
class DateTimeFilter extends StatelessWidget {
|
||||
const DateTimeFilter({super.key, this.color = Colors.white});
|
||||
const DateTimeFilter({super.key, this.color});
|
||||
|
||||
final Color color;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
|
||||
|
||||
|
|
@ -113,7 +114,7 @@ class _TextViewState extends State<TextLayer> {
|
|||
right: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(100),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
),
|
||||
child: TextField(
|
||||
controller: textController,
|
||||
|
|
@ -151,8 +152,8 @@ class _TextViewState extends State<TextLayer> {
|
|||
),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
|
|
@ -219,13 +220,13 @@ class _TextViewState extends State<TextLayer> {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(100),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
),
|
||||
child: Text(
|
||||
widget.layerData.text,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
|
|
@ -245,7 +246,9 @@ class _TextViewState extends State<TextLayer> {
|
|||
child: ActionButton(
|
||||
FontAwesomeIcons.trashCan,
|
||||
tooltipText: '',
|
||||
color: deleteLayer ? Colors.red : Colors.white,
|
||||
color: deleteLayer
|
||||
? context.appColor(AppColor.recording)
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
/// Cuts a recorded video down to the part the user wants to send.
|
||||
|
|
@ -183,7 +184,7 @@ class _VideoTrimmerState extends State<VideoTrimmer> {
|
|||
decoration: BoxDecoration(
|
||||
// Translucent rather than opaque: the frames being cut away stay
|
||||
// visible underneath, which is most of what makes a cut readable.
|
||||
color: Colors.black.withAlpha(90),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
|
|
@ -243,10 +244,15 @@ class _VideoTrimmerState extends State<VideoTrimmer> {
|
|||
: _format(_duration),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.white.withAlpha(isTrimmed ? 235 : 150),
|
||||
color: isTrimmed
|
||||
? context.appColor(AppColor.mediaForeground)
|
||||
: context.appColor(AppColor.mediaForegroundMuted),
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
shadows: const [
|
||||
Shadow(color: Color.fromARGB(122, 0, 0, 0), blurRadius: 4),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -287,7 +293,9 @@ class _TrimTrack extends StatelessWidget {
|
|||
right: handleWidth,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(46),
|
||||
color: context
|
||||
.appColor(AppColor.mediaForeground)
|
||||
.withValues(alpha: 0.18),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
|
|
@ -307,8 +315,14 @@ class _TrimTrack extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
_handle(left: startOffset),
|
||||
_handle(left: endOffset + handleWidth),
|
||||
_handle(
|
||||
left: startOffset,
|
||||
foregroundColor: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
_handle(
|
||||
left: endOffset + handleWidth,
|
||||
foregroundColor: context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
Positioned(
|
||||
left: playheadOffset + handleWidth - 1,
|
||||
top: 4,
|
||||
|
|
@ -316,10 +330,13 @@ class _TrimTrack extends StatelessWidget {
|
|||
width: 3,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Color.fromARGB(122, 0, 0, 0), blurRadius: 4),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -328,7 +345,7 @@ class _TrimTrack extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _handle({required double left}) {
|
||||
Widget _handle({required double left, required Color foregroundColor}) {
|
||||
return Positioned(
|
||||
left: left,
|
||||
top: 0,
|
||||
|
|
@ -346,7 +363,7 @@ class _TrimTrack extends StatelessWidget {
|
|||
width: 2,
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(220),
|
||||
color: foregroundColor,
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -348,7 +348,6 @@ class _ChatListViewState extends State<ChatListView>
|
|||
final plan = context.select<PurchasesProvider, SubscriptionPlan>(
|
||||
(p) => p.plan,
|
||||
);
|
||||
final dark = isDarkMode(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Row(
|
||||
|
|
@ -392,7 +391,7 @@ class _ChatListViewState extends State<ChatListView>
|
|||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: dark ? Colors.black : Colors.white,
|
||||
color: context.color.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -421,11 +420,11 @@ class _ChatListViewState extends State<ChatListView>
|
|||
),
|
||||
Center(
|
||||
child: NotificationBadgeComp(
|
||||
backgroundColor: dark ? Colors.white : Colors.black,
|
||||
textColor: dark ? Colors.black : Colors.white,
|
||||
backgroundColor: context.color.inverseSurface,
|
||||
textColor: context.color.onInverseSurface,
|
||||
count: badgeCount.toString(),
|
||||
child: IconButton(
|
||||
color: badgeCount > 0 ? Colors.black : null,
|
||||
color: badgeCount > 0 ? context.color.onPrimary : null,
|
||||
key: searchForOtherUsers,
|
||||
icon: const FaIcon(FontAwesomeIcons.userPlus, size: 18),
|
||||
onPressed: () => context.push(Routes.chatsAddNewUser),
|
||||
|
|
@ -555,12 +554,12 @@ class _ChatListViewState extends State<ChatListView>
|
|||
FloatingActionButton(
|
||||
heroTag: 'qrcode_fab',
|
||||
elevation: 2,
|
||||
backgroundColor: dark ? Colors.grey[800] : Colors.grey[200],
|
||||
foregroundColor: dark ? Colors.white : Colors.black87,
|
||||
backgroundColor: context.color.surfaceContainerHigh,
|
||||
foregroundColor: context.color.onSurface,
|
||||
onPressed: () => context.push(Routes.settingsPublicProfile),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.qrcode,
|
||||
color: dark ? Colors.white : Colors.black87,
|
||||
color: context.color.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
|
@ -568,11 +567,11 @@ class _ChatListViewState extends State<ChatListView>
|
|||
heroTag: 'new_chat_fab',
|
||||
elevation: 2,
|
||||
backgroundColor: context.color.primary,
|
||||
foregroundColor: Colors.black87,
|
||||
foregroundColor: context.color.onPrimary,
|
||||
onPressed: () => context.push(Routes.chatsStartNewChat),
|
||||
child: const FaIcon(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.penToSquare,
|
||||
color: Colors.black87,
|
||||
color: context.color.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ class NewsIconButtonComp extends StatelessWidget {
|
|||
return NotificationBadgeComp(
|
||||
count: count.toString(),
|
||||
backgroundColor: context.color.primary,
|
||||
textColor: Colors.black87,
|
||||
textColor: context.color.onPrimary,
|
||||
child: IconButton(
|
||||
onPressed: () => context.push(Routes.settingsHelpNews),
|
||||
color: Colors.grey,
|
||||
color: context.color.onSurfaceVariant,
|
||||
tooltip: context.lang.settingsHelpNews,
|
||||
icon: const FaIcon(FontAwesomeIcons.bullhorn, size: 19),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class _TypingIndicatorSubtitleCompState
|
|||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: getMessageColor(true),
|
||||
color: getMessageColor(context, true),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Transform.scale(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
|||
import 'package:twonly/src/visual/components/contact_groups.comp.dart';
|
||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/colors.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/animated_new_message.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/blink.component.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_group_action.dart';
|
||||
|
|
@ -819,8 +818,10 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
|||
}
|
||||
}
|
||||
|
||||
Color getMessageColor(bool isOther) {
|
||||
return isOther ? DefaultColors.messageSelf : DefaultColors.messageOther;
|
||||
Color getMessageColor(BuildContext context, bool isOther) {
|
||||
return isOther
|
||||
? context.appColor(AppColor.chatBubbleReceived)
|
||||
: context.appColor(AppColor.chatBubbleSent);
|
||||
}
|
||||
|
||||
class ChatItem {
|
||||
|
|
|
|||
|
|
@ -80,10 +80,10 @@ class _AllReactionsViewState extends State<AllReactionsView> {
|
|||
topRight: Radius.circular(32),
|
||||
),
|
||||
color: context.color.surface,
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 10.9,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||
color: context.color.shadow.withValues(alpha: 0.1),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -93,7 +93,7 @@ class _AllReactionsViewState extends State<AllReactionsView> {
|
|||
margin: const EdgeInsets.all(30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
color: context.color.outline,
|
||||
),
|
||||
height: 3,
|
||||
width: 60,
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ class MessageHistoryView extends StatelessWidget {
|
|||
topRight: Radius.circular(32),
|
||||
),
|
||||
color: context.color.surface,
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 10.9,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||
color: context.color.shadow.withValues(alpha: 0.1),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -43,7 +43,7 @@ class MessageHistoryView extends StatelessWidget {
|
|||
margin: const EdgeInsets.all(30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
color: context.color.outline,
|
||||
),
|
||||
height: 3,
|
||||
width: 60,
|
||||
|
|
|
|||
|
|
@ -85,10 +85,10 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
|
|||
topRight: Radius.circular(32),
|
||||
),
|
||||
color: context.color.surface,
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 10.9,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||
color: context.color.shadow.withValues(alpha: 0.1),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -98,7 +98,7 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
|
|||
margin: const EdgeInsets.only(top: 30),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
color: context.color.outline,
|
||||
),
|
||||
height: 3,
|
||||
width: 60,
|
||||
|
|
@ -145,9 +145,7 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
|
|||
color: context.color.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: icon is IconData
|
||||
? Icon(icon)
|
||||
: FaIcon(icon as FaIconData?),
|
||||
child: icon is IconData ? Icon(icon) : FaIcon(icon as FaIconData?),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(label, textAlign: TextAlign.center),
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class _WebxdcStoreViewState extends State<WebxdcStoreView> {
|
|||
margin: const EdgeInsets.only(top: 12, bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
color: context.color.outline,
|
||||
),
|
||||
height: 3,
|
||||
width: 60,
|
||||
|
|
|
|||
|
|
@ -161,13 +161,13 @@ class _ChatGroupActionState extends State<ChatGroupAction> {
|
|||
child: FaIcon(
|
||||
icon,
|
||||
size: 10,
|
||||
color: Colors.grey,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 8)),
|
||||
TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
style: TextStyle(color: context.color.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class ReactionRow extends StatelessWidget {
|
|||
// ignore: inference_failure_on_function_invocation
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: context.color.surface,
|
||||
builder: (context) {
|
||||
return AllReactionsView(
|
||||
message: message,
|
||||
|
|
@ -103,9 +103,7 @@ class ReactionRow extends StatelessWidget {
|
|||
decoration: BoxDecoration(
|
||||
border: Border.all(),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: isDarkMode(context)
|
||||
? const Color.fromARGB(255, 74, 74, 74)
|
||||
: const Color.fromARGB(255, 197, 197, 197),
|
||||
color: context.appColor(AppColor.reactionBackground),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
@ -119,9 +117,7 @@ class ReactionRow extends StatelessWidget {
|
|||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isDarkMode(context)
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
color: context.appColor(AppColor.onReaction),
|
||||
decoration: TextDecoration.none,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class _ChatAudioEntryState extends State<ChatAudioEntry> {
|
|||
? InChatAudioPlayer(
|
||||
path: mediaService.tempPath.path,
|
||||
message: message,
|
||||
foregroundColor: info.textColor,
|
||||
trailing: messageTime,
|
||||
)
|
||||
: Container())
|
||||
|
|
@ -137,12 +138,14 @@ class InChatAudioPlayer extends StatefulWidget {
|
|||
const InChatAudioPlayer({
|
||||
required this.path,
|
||||
required this.message,
|
||||
required this.foregroundColor,
|
||||
this.trailing,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String path;
|
||||
final Message message;
|
||||
final Color foregroundColor;
|
||||
|
||||
/// Shown below the waveform, next to the remaining playback time.
|
||||
final Widget? trailing;
|
||||
|
|
@ -158,11 +161,14 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
|
|||
static const double _cursorHeight = 20;
|
||||
static const double _playSlotWidth = 34;
|
||||
static const List<double> _rates = [1, 1.5, 2, 0.5];
|
||||
static const _waveStyle = PlayerWaveStyle(
|
||||
PlayerWaveStyle get _waveStyle => PlayerWaveStyle(
|
||||
spacing: 4,
|
||||
waveThickness: 2.5,
|
||||
scaleFactor: 80,
|
||||
showSeekLine: false,
|
||||
fixedWaveColor: widget.foregroundColor.withValues(alpha: 0.45),
|
||||
liveWaveColor: widget.foregroundColor,
|
||||
backgroundColor: widget.foregroundColor.withAlpha(0),
|
||||
);
|
||||
|
||||
AudioPlayback? _playback;
|
||||
|
|
@ -307,7 +313,7 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
|
|||
? FontAwesomeIcons.solidCirclePause
|
||||
: FontAwesomeIcons.solidCirclePlay,
|
||||
size: 28,
|
||||
color: Colors.white,
|
||||
color: widget.foregroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -347,7 +353,7 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
|
|||
child: Container(
|
||||
width: _cursorWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: widget.foregroundColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
|
|
@ -367,8 +373,8 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
|
|||
child: Text(
|
||||
formatMsToMinSec(remaining),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: widget.foregroundColor,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -95,12 +95,13 @@ class _ChatContactsEntryState extends State<ChatContactsEntry> {
|
|||
if (i > 0)
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
color: widget.info.textColor.withValues(alpha: 0.2),
|
||||
),
|
||||
_ContactRow(
|
||||
contact: data.contacts[i],
|
||||
message: widget.message,
|
||||
contactsById: widget.contactsById,
|
||||
foregroundColor: widget.info.textColor,
|
||||
),
|
||||
],
|
||||
],
|
||||
|
|
@ -114,11 +115,13 @@ class _ContactRow extends StatefulWidget {
|
|||
const _ContactRow({
|
||||
required this.contact,
|
||||
required this.message,
|
||||
required this.foregroundColor,
|
||||
this.contactsById,
|
||||
});
|
||||
|
||||
final SharedContact contact;
|
||||
final Message message;
|
||||
final Color foregroundColor;
|
||||
final Map<int, Contact>? contactsById;
|
||||
|
||||
@override
|
||||
|
|
@ -220,36 +223,36 @@ class _ContactRowState extends State<_ContactRow> {
|
|||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.user,
|
||||
color: Colors.white,
|
||||
color: widget.foregroundColor,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: BetterText(
|
||||
text: widget.contact.displayName,
|
||||
textColor: Colors.white,
|
||||
textColor: widget.foregroundColor,
|
||||
),
|
||||
),
|
||||
if (widget.message.senderId != null && !isAdded) ...[
|
||||
const Spacer(),
|
||||
const SizedBox(width: 8),
|
||||
if (_isLoading)
|
||||
const SizedBox(
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white,
|
||||
widget.foregroundColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.userPlus,
|
||||
color: Colors.white,
|
||||
color: widget.foregroundColor,
|
||||
size: 16,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ class ChatDateChip extends StatelessWidget {
|
|||
return Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDarkMode(context)
|
||||
? const Color.fromARGB(255, 38, 38, 38)
|
||||
: Colors.black.withAlpha(40),
|
||||
color: context.color.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||
|
|
@ -43,7 +41,7 @@ class ChatDateChip extends StatelessWidget {
|
|||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: isDarkMode(context) ? Colors.white : Colors.grey,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class ChatFlameRestoredEntry extends StatelessWidget {
|
|||
text: context.lang.chatEntryFlameRestored(
|
||||
data.restoredFlameCounter.toInt(),
|
||||
),
|
||||
textColor: isDarkMode(context) ? Colors.black : Colors.black,
|
||||
textColor: info.textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ import 'package:twonly/src/services/mediafiles/media_download_policy.dart'
|
|||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/better_text.element.dart';
|
||||
import 'package:twonly/src/visual/themes/colors.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/common.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/in_chat_media_viewer.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_color.dart';
|
||||
import 'package:twonly/src/visual/views/chats/media_viewer.view.dart';
|
||||
|
||||
class ChatMediaEntry extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -15,14 +15,12 @@ class ChatUnknownEntry extends StatelessWidget {
|
|||
),
|
||||
padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isDarkMode(context) ? Colors.black : Colors.grey,
|
||||
color: context.appColor(AppColor.chatBubbleDeleted),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: BetterText(
|
||||
text: context.lang.updateTwonlyMessage,
|
||||
textColor: isDarkMode(context)
|
||||
? const Color.fromARGB(255, 99, 99, 99)
|
||||
: Colors.black,
|
||||
textColor: context.appColor(AppColor.onChatBubbleDeleted),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ BubbleInfo getBubbleInfo(
|
|||
) {
|
||||
final info = BubbleInfo()
|
||||
..text = message.content ?? ''
|
||||
..textColor = Colors.white
|
||||
..color = getMessageColor(message.senderId != null)
|
||||
..textColor = message.senderId != null
|
||||
? context.appColor(AppColor.onChatBubbleReceived)
|
||||
: context.appColor(AppColor.onChatBubbleSent)
|
||||
..color = getMessageColor(context, message.senderId != null)
|
||||
..displayTime = !combineTextMessageWithNext(message, nextMessage)
|
||||
..displayUserName = ''
|
||||
..minWidth = minWidth
|
||||
|
|
@ -61,7 +63,7 @@ BubbleInfo getBubbleInfo(
|
|||
..expanded = false
|
||||
..color = message.quotesMessageId != null
|
||||
? Colors.transparent
|
||||
: getMessageColor(message.senderId != null);
|
||||
: getMessageColor(context, message.senderId != null);
|
||||
if (message.isDeletedFromSender) {
|
||||
info
|
||||
..color = context.color.surfaceBright
|
||||
|
|
@ -73,10 +75,8 @@ BubbleInfo getBubbleInfo(
|
|||
if (message.isDeletedFromSender) {
|
||||
info
|
||||
..text = context.lang.messageWasDeleted
|
||||
..color = isDarkMode(context) ? Colors.black : Colors.grey;
|
||||
if (isDarkMode(context)) {
|
||||
info.textColor = const Color.fromARGB(255, 99, 99, 99);
|
||||
}
|
||||
..color = context.appColor(AppColor.chatBubbleDeleted)
|
||||
..textColor = context.appColor(AppColor.onChatBubbleDeleted);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ class FriendlyMessageTime extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusIcon = _buildStatusIcon(context, Colors.grey.shade400);
|
||||
final statusIcon = _buildStatusIcon(
|
||||
context,
|
||||
context.appColor(AppColor.chatMessageMeta),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
|
|
@ -34,7 +37,7 @@ class FriendlyMessageTime extends StatelessWidget {
|
|||
height: 10,
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.pencil,
|
||||
color: color ?? Colors.white.withAlpha(150),
|
||||
color: color ?? context.appColor(AppColor.chatMessageMeta),
|
||||
size: 10,
|
||||
),
|
||||
),
|
||||
|
|
@ -48,7 +51,7 @@ class FriendlyMessageTime extends StatelessWidget {
|
|||
),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: color ?? Colors.white.withAlpha(150),
|
||||
color: color ?? context.appColor(AppColor.chatMessageMeta),
|
||||
decoration: TextDecoration.none,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -145,10 +145,7 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
|||
minHeight: 39,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.info.color.withValues(alpha: 0.3),
|
||||
border: Border.all(
|
||||
color: widget.info.color.withValues(alpha: 0.4),
|
||||
),
|
||||
color: widget.info.color,
|
||||
borderRadius: widget.borderRadius,
|
||||
),
|
||||
child: Padding(
|
||||
|
|
@ -166,9 +163,7 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
|||
if (widget.info.displayTime || widget.message.modifiedAt != null)
|
||||
FriendlyMessageTime(
|
||||
message: widget.message,
|
||||
color: isDarkMode(context)
|
||||
? Colors.white.withAlpha(100)
|
||||
: Colors.black.withAlpha(100),
|
||||
color: context.appColor(AppColor.chatMessageMeta),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ class MessageContextMenu extends StatelessWidget {
|
|||
final layer =
|
||||
await showModalBottomSheet(
|
||||
context: navigator.context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: context.color.surface,
|
||||
builder: (context) {
|
||||
return const EmojiPickerBottom();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ class _MessageInputState extends State<MessageInput>
|
|||
// ignore: inference_failure_on_function_invocation
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: context.color.surface,
|
||||
builder: (context) {
|
||||
return ShareAdditionalView(
|
||||
group: widget.group,
|
||||
|
|
@ -457,10 +457,12 @@ class _MessageInputState extends State<MessageInput>
|
|||
),
|
||||
child: FadeTransition(
|
||||
opacity: _recordingBlink,
|
||||
child: const FaIcon(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.microphone,
|
||||
size: 20,
|
||||
color: Colors.red,
|
||||
color: context.appColor(
|
||||
AppColor.recording,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -470,9 +472,7 @@ class _MessageInputState extends State<MessageInput>
|
|||
_currentDuration,
|
||||
),
|
||||
style: TextStyle(
|
||||
color: isDarkMode(context)
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
color: context.color.onSurface,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
|
|
@ -491,8 +491,10 @@ class _MessageInputState extends State<MessageInput>
|
|||
onTap: _cancelAudioRecording,
|
||||
child: Text(
|
||||
context.lang.voiceMessageCancel,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
style: TextStyle(
|
||||
color: context.appColor(
|
||||
AppColor.recording,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -591,9 +593,7 @@ class _MessageInputState extends State<MessageInput>
|
|||
borderRadius: BorderRadius.circular(
|
||||
90,
|
||||
),
|
||||
color: isDarkMode(context)
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
color: context.color.surface,
|
||||
),
|
||||
child: const Center(
|
||||
child: Column(
|
||||
|
|
@ -622,7 +622,9 @@ class _MessageInputState extends State<MessageInput>
|
|||
right: -20,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
color: context.appColor(
|
||||
AppColor.recording,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(90),
|
||||
),
|
||||
width: 60,
|
||||
|
|
@ -644,7 +646,7 @@ class _MessageInputState extends State<MessageInput>
|
|||
color:
|
||||
(_recordingState ==
|
||||
RecordingState.recording)
|
||||
? Colors.white
|
||||
? context.color.onError
|
||||
: null,
|
||||
(_recordingState == RecordingState.none)
|
||||
? FontAwesomeIcons.microphone
|
||||
|
|
@ -712,11 +714,11 @@ class _MessageInputState extends State<MessageInput>
|
|||
),
|
||||
searchViewConfig: SearchViewConfig(
|
||||
backgroundColor: context.color.surfaceContainer,
|
||||
buttonIconColor: Colors.white,
|
||||
buttonIconColor: context.color.onSurface,
|
||||
),
|
||||
categoryViewConfig: CategoryViewConfig(
|
||||
backgroundColor: context.color.surfaceContainer,
|
||||
dividerColor: Colors.white,
|
||||
dividerColor: context.color.outlineVariant,
|
||||
indicatorColor: context.color.primary,
|
||||
iconColorSelected: context.color.primary,
|
||||
iconColor: context.color.secondary,
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import 'package:twonly/src/database/tables/messages.table.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/animate_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/colors.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/friendly_message_time.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/file_limit_reached.dialog.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_color.dart';
|
||||
|
||||
enum MessageSendState {
|
||||
received,
|
||||
|
|
@ -310,7 +310,11 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
widget.group!.draftMessage != null &&
|
||||
widget.group!.draftMessage != '') {
|
||||
icons = [
|
||||
const FaIcon(FontAwesomeIcons.pen, size: 12, color: Colors.grey),
|
||||
FaIcon(
|
||||
FontAwesomeIcons.pen,
|
||||
size: 12,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
];
|
||||
textWidget = Text(
|
||||
'${context.lang.draftMessage}: ${substringBy(widget.group!.draftMessage!, 10)}',
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class ResponseContainer extends StatelessWidget {
|
|||
maxWidth: MediaQuery.sizeOf(context).width * 0.8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getMessageColor(msg.senderId != null),
|
||||
color: getMessageColor(context, msg.senderId != null),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: IntrinsicWidth(
|
||||
|
|
@ -187,7 +187,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String? subtitle;
|
||||
var color = const Color.fromARGB(233, 68, 137, 255);
|
||||
var color = context.appColor(AppColor.messageText);
|
||||
|
||||
if (_message != null) {
|
||||
if (_message!.type == MessageType.text.name) {
|
||||
|
|
@ -238,7 +238,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
|||
subtitle = _storyAlive ? context.lang.story : context.lang.storyExpired;
|
||||
}
|
||||
|
||||
color = getMessageColor(_message!.senderId != null);
|
||||
color = getMessageColor(context, _message!.senderId != null);
|
||||
}
|
||||
|
||||
final hasImage =
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages.view.dart';
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ class _TypingIndicatorState extends State<TypingIndicator> {
|
|||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getMessageColor(true),
|
||||
color: getMessageColor(context, true),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: AnimatedTypingDots(
|
||||
|
|
@ -246,8 +247,8 @@ class _AnimatedDot extends AnimatedWidget {
|
|||
child: Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
decoration: BoxDecoration(
|
||||
color: context.appColor(AppColor.onChatBubbleReceived),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -870,10 +870,10 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
fontWeight: FontWeight.bold,
|
||||
color: showSendTextMessageInput
|
||||
? null
|
||||
: const Color.fromARGB(255, 126, 126, 126),
|
||||
shadows: const [
|
||||
: context.appColor(AppColor.mediaForegroundMuted),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Color.fromARGB(122, 0, 0, 0),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:photo_view/photo_view.dart';
|
|||
import 'package:twonly/src/database/tables/mediafiles.table.dart'
|
||||
show MediaType;
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class MediaContentRenderer extends StatelessWidget {
|
||||
|
|
@ -47,10 +48,10 @@ class MediaContentRenderer extends StatelessWidget {
|
|||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Center(
|
||||
return Center(
|
||||
child: Icon(
|
||||
Icons.broken_image_outlined,
|
||||
color: Colors.white38,
|
||||
color: context.appColor(AppColor.mediaForegroundMuted),
|
||||
size: 64,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/stories.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/my_icon_button.element.dart';
|
||||
import 'package:twonly/src/visual/views/chats/media_viewer_components/story_viewers.bottom_sheet.dart';
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ class OwnStoryBottomBar extends StatelessWidget {
|
|||
Future<void> _showViewers(BuildContext context, MediaFile media) async {
|
||||
await showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: context.appColor(AppColor.mediaBackground),
|
||||
builder: (context) => StoryViewersBottomSheet(mediaFile: media),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,7 +283,10 @@ class EmojiFloatWidgetState extends State<EmojiFloatWidget>
|
|||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: CustomPaint(
|
||||
painter: _ParticlePainter(List<_Particle>.from(_particles)),
|
||||
painter: _ParticlePainter(
|
||||
List<_Particle>.from(_particles),
|
||||
context.appColor(AppColor.mediaBackground),
|
||||
),
|
||||
size: Size.infinite,
|
||||
),
|
||||
);
|
||||
|
|
@ -339,8 +342,9 @@ class _Particle {
|
|||
}
|
||||
|
||||
class _ParticlePainter extends CustomPainter {
|
||||
_ParticlePainter(this.particles);
|
||||
_ParticlePainter(this.particles, this.particleColor);
|
||||
final List<_Particle> particles;
|
||||
final Color particleColor;
|
||||
|
||||
static final _textPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||
|
||||
|
|
@ -351,7 +355,7 @@ class _ParticlePainter extends CustomPainter {
|
|||
text: p.emoji,
|
||||
style: TextStyle(
|
||||
fontSize: 24 * p.currentScale,
|
||||
color: Colors.black.withValues(alpha: p.opacity),
|
||||
color: particleColor.withValues(alpha: p.opacity),
|
||||
fontFamily: Platform.isAndroid ? 'sans-serif' : null,
|
||||
fontFamilyFallback: Platform.isAndroid
|
||||
? const ['NotoColorEmoji']
|
||||
|
|
|
|||
|
|
@ -94,10 +94,10 @@ class _StoryViewersBottomSheetState extends State<StoryViewersBottomSheet> {
|
|||
topRight: Radius.circular(32),
|
||||
),
|
||||
color: context.color.surface,
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 10.9,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||
color: context.color.shadow.withValues(alpha: 0.1),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -107,7 +107,7 @@ class _StoryViewersBottomSheetState extends State<StoryViewersBottomSheet> {
|
|||
margin: const EdgeInsets.only(top: 20, bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
color: Colors.grey,
|
||||
color: context.color.outline,
|
||||
),
|
||||
height: 3,
|
||||
width: 60,
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ class _MessageInfoViewState extends State<MessageInfoView> {
|
|||
// ignore: inference_failure_on_function_invocation
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: context.color.surface,
|
||||
builder: (context) {
|
||||
return MessageHistoryView(
|
||||
message: widget.message,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
|||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/context_menu/group.context_menu.dart';
|
||||
import 'package:twonly/src/visual/context_menu/user.context_menu.dart';
|
||||
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
||||
import 'package:twonly/src/visual/elements/my_input.element.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages.view.dart';
|
||||
|
||||
class StartNewChatView extends StatefulWidget {
|
||||
|
|
@ -65,6 +65,7 @@ class _StartNewChatView extends State<StartNewChatView> {
|
|||
void dispose() {
|
||||
allNonDirectGroupsSub.cancel();
|
||||
contactSub.cancel();
|
||||
searchUserName.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -142,22 +143,20 @@ class _StartNewChatView extends State<StartNewChatView> {
|
|||
padding: const EdgeInsets.only(
|
||||
bottom: 40,
|
||||
left: 10,
|
||||
top: 20,
|
||||
top: 0,
|
||||
right: 10,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: TextField(
|
||||
onChanged: (_) async {
|
||||
await filterUsers();
|
||||
},
|
||||
child: MyInput(
|
||||
controller: searchUserName,
|
||||
decoration: getInputDecoration(
|
||||
context,
|
||||
context.lang.startNewChatSearchHint,
|
||||
),
|
||||
dense: true,
|
||||
fontWeight: FontWeight.normal,
|
||||
hintText: context.lang.startNewChatSearchHint,
|
||||
prefixIcon: const Icon(Icons.search, size: 20),
|
||||
onChanged: (_) => unawaited(filterUsers()),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
|
@ -171,12 +170,15 @@ class _StartNewChatView extends State<StartNewChatView> {
|
|||
if (i == 0) {
|
||||
return ListTile(
|
||||
title: Text(context.lang.newGroup),
|
||||
leading: const CircleAvatar(
|
||||
leading: const SizedBox.square(
|
||||
dimension: 26,
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.userGroup,
|
||||
size: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () => context.push(
|
||||
Routes.groupCreateSelectMember(null),
|
||||
),
|
||||
|
|
@ -185,12 +187,15 @@ class _StartNewChatView extends State<StartNewChatView> {
|
|||
if (i == 1) {
|
||||
return ListTile(
|
||||
title: Text(context.lang.startNewChatNewContact),
|
||||
leading: const CircleAvatar(
|
||||
leading: const SizedBox.square(
|
||||
dimension: 26,
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.userPlus,
|
||||
size: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () => context.push(Routes.chatsAddNewUser),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ class OpenRequestsListComp extends StatelessWidget {
|
|||
).merge(secondaryGreyButtonStyle(context)),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.person_off_rounded,
|
||||
color: Color.fromARGB(164, 244, 67, 54),
|
||||
color: context.color.error,
|
||||
),
|
||||
Text(
|
||||
context.lang.contactActionBlock,
|
||||
|
|
@ -89,7 +89,7 @@ class OpenRequestsListComp extends StatelessWidget {
|
|||
).merge(secondaryGreyButtonStyle(context)),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.check, color: Colors.green),
|
||||
Icon(Icons.check, color: context.appColor(AppColor.success)),
|
||||
Text(
|
||||
context.lang.contactActionAccept,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
|||
import 'package:twonly/src/visual/elements/better_list_title.element.dart';
|
||||
import 'package:twonly/src/visual/views/contact/contact_components/mutual_groups_expansion_tile.comp.dart';
|
||||
import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart';
|
||||
import 'package:twonly/src/visual/views/contact/contact_components/user_discovery_contact_settings.comp.dart';
|
||||
import 'package:twonly/src/visual/views/contact/contact_components/verification_expansion_tile.comp.dart';
|
||||
import 'package:twonly/src/visual/views/contact/select_contact_groups.view.dart';
|
||||
import 'package:twonly/src/visual/views/groups/group.view.dart';
|
||||
|
|
@ -301,9 +300,6 @@ class _ContactViewState extends State<ContactView> {
|
|||
MutualGroupsExpansionTileComp(
|
||||
contact: contact,
|
||||
),
|
||||
UserDiscoveryContactSettingsComp(
|
||||
contact: contact,
|
||||
),
|
||||
const Divider(),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.flag,
|
||||
|
|
@ -331,7 +327,7 @@ class _ContactViewState extends State<ContactView> {
|
|||
BetterListTile(
|
||||
icon: FontAwesomeIcons.userMinus,
|
||||
iconSize: 16,
|
||||
color: Colors.red,
|
||||
color: context.color.error,
|
||||
text: context.lang.contactRemove,
|
||||
onTap: () => handleUserRemoveRequest(contact),
|
||||
),
|
||||
|
|
@ -382,12 +378,16 @@ class _ContactViewState extends State<ContactView> {
|
|||
SignalVersion.v1 => 'Signal Protocol (v1)',
|
||||
SignalVersion.v2 => 'PQXDH (v2)',
|
||||
}}',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
style: TextStyle(
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Can send widgets: ${contact.widgetSharingAllowed ? 'Yes' : 'No'}',
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
style: TextStyle(
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
|
|||
Future<void> _selectEmoji() async {
|
||||
final result = await showModalBottomSheet<dynamic>(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: context.color.surface,
|
||||
builder: (context) => const EmojiPickerBottom(),
|
||||
);
|
||||
if (result is EmojiLayerData && mounted) {
|
||||
|
|
@ -330,9 +330,7 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
|
|||
color: color == noContactGroupBackgroundColor ? null : Color(color),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? (isDarkMode(context) ? Colors.white : Colors.black)
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
color: selected ? context.color.onSurface : context.color.outline,
|
||||
width: selected ? 3 : 1,
|
||||
),
|
||||
),
|
||||
|
|
@ -431,10 +429,10 @@ class _ContactGroupSettingsViewState extends State<ContactGroupSettingsView> {
|
|||
IconButton(
|
||||
tooltip: context.lang.deleteContactGroup,
|
||||
onPressed: _delete,
|
||||
icon: const FaIcon(
|
||||
icon: FaIcon(
|
||||
FontAwesomeIcons.trashCan,
|
||||
size: 18,
|
||||
color: Colors.red,
|
||||
color: context.color.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ class CriticalErrorView extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.triangleExclamation,
|
||||
size: 80,
|
||||
color: Colors.redAccent,
|
||||
color: context.color.error,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ class _GroupViewState extends State<GroupView> {
|
|||
if (!_group!.leftGroup)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.rightFromBracket,
|
||||
color: Colors.red,
|
||||
color: context.color.error,
|
||||
text: context.lang.leaveGroup,
|
||||
onTap: _leaveGroup,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class MemoriesCloudBackupPromoComp extends StatelessWidget {
|
|||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
color: context.color.shadow.withValues(alpha: 0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
|
|||
width: 120,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12,
|
||||
color: context.color.shadow.withValues(alpha: 0.12),
|
||||
blurRadius: 6,
|
||||
offset: Offset(0, 2),
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -68,11 +68,15 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
|
|||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return ColoredBox(
|
||||
color: Colors.grey.shade800,
|
||||
child: const Center(
|
||||
color: context.appColor(
|
||||
AppColor.mediaBackground,
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.broken_image_outlined,
|
||||
color: Colors.white30,
|
||||
color: context.appColor(
|
||||
AppColor.mediaForegroundMuted,
|
||||
),
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
|
|
@ -86,7 +90,7 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
|
|||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.center,
|
||||
colors: [
|
||||
Colors.black.withValues(alpha: 0.7),
|
||||
context.appColor(AppColor.mediaScrim),
|
||||
Colors.transparent,
|
||||
],
|
||||
),
|
||||
|
|
@ -100,8 +104,10 @@ class MemoriesFlashbackBannerComp extends StatelessWidget {
|
|||
child: Text(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: context.appColor(
|
||||
AppColor.mediaForeground,
|
||||
),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
|||
import 'package:twonly/src/model/memory_item.model.dart';
|
||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/selectable_thumbnail.comp.dart';
|
||||
import 'package:twonly/src/visual/views/memories/components/memory_transition_painter.dart';
|
||||
|
||||
|
|
@ -234,11 +235,11 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
|||
}
|
||||
Log.warn(error);
|
||||
return ColoredBox(
|
||||
color: Colors.grey.shade200,
|
||||
child: const Center(
|
||||
color: context.color.surfaceContainerHigh,
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.image,
|
||||
color: Colors.black26,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -251,37 +252,43 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
|||
)
|
||||
else
|
||||
ColoredBox(
|
||||
color: Colors.grey.shade200,
|
||||
child: const Center(
|
||||
color: context.color.surfaceContainerHigh,
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.image,
|
||||
color: Colors.black26,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isVideo)
|
||||
const Positioned.fill(
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.circlePlay,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 32,
|
||||
shadows: [
|
||||
Shadow(color: Colors.black54, blurRadius: 6),
|
||||
Shadow(
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (media.mediaFile.isFavorite)
|
||||
const Positioned(
|
||||
Positioned(
|
||||
bottom: 6,
|
||||
left: 6,
|
||||
child: Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.redAccent,
|
||||
color: context.color.error,
|
||||
size: 16,
|
||||
shadows: [
|
||||
Shadow(color: Colors.black54, blurRadius: 4),
|
||||
Shadow(
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -294,17 +301,17 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
|||
switch (media.mediaFile.cloudState) {
|
||||
case CloudState.none:
|
||||
iconData = Icons.cloud_off_outlined;
|
||||
color = Colors.white54;
|
||||
color = context.appColor(AppColor.mediaForegroundMuted);
|
||||
case CloudState.pending:
|
||||
iconData = Icons.cloud_upload_outlined;
|
||||
color = Colors.white70;
|
||||
color = context.appColor(AppColor.mediaForegroundMuted);
|
||||
case CloudState.uploaded:
|
||||
if (hasStored) {
|
||||
iconData = Icons.cloud_done_outlined;
|
||||
color = Colors.white;
|
||||
color = context.appColor(AppColor.mediaForeground);
|
||||
} else {
|
||||
iconData = Icons.cloud_outlined;
|
||||
color = Colors.white;
|
||||
color = context.appColor(AppColor.mediaForeground);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -315,8 +322,11 @@ class _MemoriesThumbnailCompState extends State<MemoriesThumbnailComp> {
|
|||
iconData,
|
||||
color: color,
|
||||
size: 16,
|
||||
shadows: const [
|
||||
Shadow(color: Colors.black54, blurRadius: 4),
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -85,15 +85,15 @@ class MemoriesSelectionMenuComp extends StatelessWidget {
|
|||
value: 'delete',
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.delete_forever_outlined,
|
||||
size: 20,
|
||||
color: Colors.redAccent,
|
||||
color: context.color.error,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
context.lang.memoriesMenuDelete,
|
||||
style: const TextStyle(color: Colors.redAccent),
|
||||
style: TextStyle(color: context.color.error),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(12),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.50),
|
||||
color: context.appColor(AppColor.mediaScrim),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
|
|
@ -40,26 +40,28 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
|
|||
if (showStoreButton)
|
||||
_ToolbarAction(
|
||||
icon: isImageSaving
|
||||
? const SizedBox(
|
||||
? SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation(Colors.white),
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
context.appColor(AppColor.mediaForeground),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const FaIcon(
|
||||
: FaIcon(
|
||||
FontAwesomeIcons.floppyDisk,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 18,
|
||||
),
|
||||
label: context.lang.galleryActionSave,
|
||||
onTap: isImageSaving ? null : onStore,
|
||||
),
|
||||
_ToolbarAction(
|
||||
icon: const FaIcon(
|
||||
icon: FaIcon(
|
||||
FontAwesomeIcons.download,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 19,
|
||||
),
|
||||
label: context.lang.galleryActionExport,
|
||||
|
|
@ -68,7 +70,9 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
|
|||
_ToolbarAction(
|
||||
icon: Icon(
|
||||
isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
color: isFavorite ? Colors.redAccent : Colors.white,
|
||||
color: isFavorite
|
||||
? context.color.error
|
||||
: context.appColor(AppColor.mediaForeground),
|
||||
size: 22,
|
||||
),
|
||||
label: isFavorite
|
||||
|
|
@ -77,18 +81,18 @@ class SynchronizedViewerActionsToolbarComp extends StatelessWidget {
|
|||
onTap: onToggleFavorite,
|
||||
),
|
||||
_ToolbarAction(
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 22,
|
||||
),
|
||||
label: context.lang.galleryActionDelete,
|
||||
onTap: onDelete,
|
||||
),
|
||||
_ToolbarAction(
|
||||
icon: const FaIcon(
|
||||
icon: FaIcon(
|
||||
FontAwesomeIcons.shareNodes,
|
||||
color: Colors.white,
|
||||
color: context.appColor(AppColor.mediaForeground),
|
||||
size: 19,
|
||||
),
|
||||
label: context.lang.galleryActionShare,
|
||||
|
|
@ -135,7 +139,7 @@ class _ToolbarAction extends StatelessWidget {
|
|||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
color: context.appColor(AppColor.mediaForegroundMuted),
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.1,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:photo_view/photo_view.dart';
|
|||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/model/memory_item.model.dart';
|
||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/helpers/video_player_file.helper.dart';
|
||||
|
||||
class SynchronizedViewerItemComp extends StatefulWidget {
|
||||
|
|
@ -124,10 +125,10 @@ class _SynchronizedViewerItemCompState
|
|||
hash: item.mediaService.mediaFile.blurhash!,
|
||||
optimizationMode: BlurHashOptimizationMode.approximation,
|
||||
)
|
||||
: const Center(
|
||||
: Center(
|
||||
child: Icon(
|
||||
Icons.broken_image_outlined,
|
||||
color: Colors.white38,
|
||||
color: context.appColor(AppColor.mediaForegroundMuted),
|
||||
size: 64,
|
||||
),
|
||||
)
|
||||
|
|
@ -143,10 +144,10 @@ class _SynchronizedViewerItemCompState
|
|||
color: Colors.transparent,
|
||||
),
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Center(
|
||||
return Center(
|
||||
child: Icon(
|
||||
Icons.broken_image_outlined,
|
||||
color: Colors.white38,
|
||||
color: context.appColor(AppColor.mediaForegroundMuted),
|
||||
size: 64,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -441,14 +441,14 @@ class MemoriesViewState extends State<MemoriesView>
|
|||
Icon(
|
||||
Icons.photo_library_outlined,
|
||||
size: 64,
|
||||
color: Colors.grey.shade400,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.lang.memoriesEmpty,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
style: TextStyle(
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -489,7 +489,7 @@ class MemoriesViewState extends State<MemoriesView>
|
|||
children: [
|
||||
if (_isUsageLimitReached)
|
||||
Container(
|
||||
color: Colors.redAccent,
|
||||
color: context.color.error,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
|
|
@ -497,8 +497,8 @@ class MemoriesViewState extends State<MemoriesView>
|
|||
),
|
||||
child: Text(
|
||||
context.lang.memoriesBackupLimitReached,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: context.color.onError,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
|
|
@ -656,7 +656,7 @@ class MemoriesViewState extends State<MemoriesView>
|
|||
? Icons.favorite
|
||||
: Icons.favorite_border,
|
||||
color: _filterFavoritesOnly
|
||||
? Colors.redAccent
|
||||
? context.color.error
|
||||
: null,
|
||||
),
|
||||
onPressed: () {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue