Initial commit
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2026-09-11 15:59:56 -03:00
commit 0037b0b9cf
651 changed files with 199705 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/config/app_config.dart';
import 'package:tatchat/config/themes.dart';
import 'package:tatchat/pages/call/call_page.dart';
import 'package:tatchat/widgets/matrix.dart';
import 'package:material_ui/material_ui.dart';
class CallOverlay extends StatelessWidget {
final Widget child;
const CallOverlay({required this.child, super.key});
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: Matrix.of(context).activeCallRoomId,
builder: (context, value, _) {
final roomId = value;
if (roomId == null) return child;
return ValueListenableBuilder(
valueListenable: Matrix.of(context).callPosition,
builder: (context, value, _) {
final callPosition = value;
final callFullScreen = callPosition == .fullScreen;
return Stack(
children: [
child,
AnimatedPositioned(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
top: callFullScreen
? 0
: callPosition == .top
? 86
: null,
left: callFullScreen ? 0 : null,
right: callFullScreen ? 0 : 8,
bottom: callFullScreen ? 0 : null,
height: callFullScreen ? null : 256,
width: callFullScreen ? null : 256,
child: Material(
elevation: callFullScreen ? 0 : 4,
clipBehavior: .hardEdge,
shape: callFullScreen
? null
: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey),
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
),
child: Navigator(
onDidRemovePage: (_) {
Matrix.of(context).activeCallRoomId.value = null;
},
pages: [MaterialPage(child: CallPage(roomId: roomId))],
),
),
),
],
);
},
);
},
);
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'dart:math';
import 'package:material_ui/material_ui.dart';
class EmptyPage extends StatelessWidget {
static const double _width = 400;
const EmptyPage({super.key});
@override
Widget build(BuildContext context) {
final width = min(MediaQuery.sizeOf(context).width, EmptyPage._width) / 2;
final theme = Theme.of(context);
return Scaffold(
// Add invisible appbar to make status bar on Android tablets bright.
appBar: AppBar(
automaticallyImplyLeading: false,
elevation: 0,
backgroundColor: Colors.transparent,
),
extendBodyBehindAppBar: true,
body: Container(
alignment: Alignment.center,
child: Image.asset(
'assets/logo/mini/logo_mono_mini.png',
color: theme.colorScheme.surfaceContainerHigh,
width: width,
height: width,
filterQuality: FilterQuality.medium,
),
),
);
}
}

View File

@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/config/app_config.dart';
import 'package:tatchat/config/setting_keys.dart';
import 'package:tatchat/config/themes.dart';
import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/utils/platform_infos.dart';
import 'package:material_ui/material_ui.dart';
import 'package:particles_network/particles_network.dart';
import 'package:url_launcher/url_launcher_string.dart';
class LoginScaffold extends StatelessWidget {
final Widget body;
final AppBar? appBar;
final Widget? bottomNavigationBar;
const LoginScaffold({
super.key,
required this.body,
this.appBar,
this.bottomNavigationBar,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return LayoutBuilder(
builder: (context, constraints) {
final isMobileMode = !FluffyThemes.isColumnModeByWidth(
constraints.maxWidth,
);
if (isMobileMode) {
return Scaffold(
key: const Key('LoginScaffold'),
appBar: appBar,
body: SafeArea(child: body),
bottomNavigationBar: bottomNavigationBar,
);
}
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.surfaceContainerLow,
theme.colorScheme.surfaceContainer,
theme.colorScheme.surfaceContainerHighest,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Stack(
children: [
if (!MediaQuery.disableAnimationsOf(context))
ParticleNetwork(
maxSpeed: 0.25,
particleColor: theme.colorScheme.primary,
lineColor: theme.colorScheme.secondary,
),
Column(
children: [
const SizedBox(height: 16),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Material(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
clipBehavior: Clip.hardEdge,
elevation:
theme.appBarTheme.scrolledUnderElevation ?? 4,
shadowColor: theme.appBarTheme.shadowColor,
child: ConstrainedBox(
constraints: isMobileMode
? const BoxConstraints()
: const BoxConstraints(
maxWidth: 480,
maxHeight: 640,
),
child: Scaffold(
key: const Key('LoginScaffold'),
appBar: appBar,
body: SafeArea(child: body),
bottomNavigationBar: bottomNavigationBar,
),
),
),
),
),
),
const _PrivacyButtons(mainAxisAlignment: .center),
],
),
],
),
);
},
);
}
}
class _PrivacyButtons extends StatelessWidget {
final MainAxisAlignment mainAxisAlignment;
const _PrivacyButtons({required this.mainAxisAlignment});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final shadowTextStyle = TextStyle(color: theme.colorScheme.secondary);
return SizedBox(
height: 64,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: mainAxisAlignment,
children: [
TextButton(
onPressed: () => launchUrlString(AppSettings.website.value),
child: Text(L10n.of(context).website, style: shadowTextStyle),
),
TextButton(
onPressed: () => launchUrlString(AppConfig.supportUrl),
child: Text(L10n.of(context).help, style: shadowTextStyle),
),
TextButton(
onPressed: () => launchUrlString(AppSettings.privacyPolicy.value),
child: Text(L10n.of(context).privacy, style: shadowTextStyle),
),
TextButton(
onPressed: () => PlatformInfos.showDialog(context),
child: Text(L10n.of(context).about, style: shadowTextStyle),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/config/app_config.dart';
import 'package:tatchat/config/themes.dart';
import 'package:material_ui/material_ui.dart';
class MaxWidthBody extends StatelessWidget {
final Widget child;
final double maxWidth;
final bool withScrolling;
final EdgeInsets? innerPadding;
const MaxWidthBody({
required this.child,
this.maxWidth = 600,
this.withScrolling = true,
this.innerPadding,
super.key,
});
@override
Widget build(BuildContext context) {
return SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
final theme = Theme.of(context);
const desiredWidth = FluffyThemes.columnWidth * 1.5;
final body = constraints.maxWidth <= desiredWidth
? child
: Container(
alignment: Alignment.topCenter,
padding: const EdgeInsets.all(32),
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5,
),
child: Material(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
side: BorderSide(color: theme.dividerColor),
),
clipBehavior: Clip.hardEdge,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: child,
),
),
),
);
if (!withScrolling) return body;
return SingleChildScrollView(
padding: innerPadding,
physics: const ScrollPhysics(),
child: body,
);
},
),
);
}
}

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/config/themes.dart';
import 'package:material_ui/material_ui.dart';
class TwoColumnLayout extends StatelessWidget {
final Widget mainView;
final Widget sideView;
final bool hasNavigationRail;
const TwoColumnLayout({
super.key,
required this.mainView,
required this.sideView,
this.hasNavigationRail = true,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return ScaffoldMessenger(
child: Scaffold(
body: Row(
children: [
Container(
clipBehavior: Clip.antiAlias,
decoration: const BoxDecoration(),
width:
FluffyThemes.columnWidth +
(hasNavigationRail ? FluffyThemes.navRailWidth : 0),
child: mainView,
),
Container(width: 1.0, color: theme.dividerColor),
Expanded(child: ClipRRect(child: sideView)),
],
),
),
);
}
}