Merge branch 'main' into new-date-picker

pull/795/head
ggurdin 2 years ago
commit 8f3acf2b12

@ -10,7 +10,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: cat .github/workflows/versions.env >> $GITHUB_ENV - run: cat .github/workflows/versions.env >> $GITHUB_ENV
- run: echo "$WEB_APP_ENV" > .env
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -31,7 +30,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: cat .github/workflows/versions.env >> $GITHUB_ENV - run: cat .github/workflows/versions.env >> $GITHUB_ENV
- run: echo "$WEB_APP_ENV" > .env
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: ${{ env.JAVA_VERSION }} java-version: ${{ env.JAVA_VERSION }}
@ -47,7 +45,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: cat .github/workflows/versions.env >> $GITHUB_ENV - run: cat .github/workflows/versions.env >> $GITHUB_ENV
- run: echo "$WEB_APP_ENV" > .env
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -62,7 +59,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: cat .github/workflows/versions.env >> $GITHUB_ENV - run: cat .github/workflows/versions.env >> $GITHUB_ENV
- run: echo "$WEB_APP_ENV" > .env
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -77,7 +73,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: cat .github/workflows/versions.env >> $GITHUB_ENV - run: cat .github/workflows/versions.env >> $GITHUB_ENV
- run: echo "$WEB_APP_ENV" > .env
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}

@ -4,15 +4,18 @@ on:
push: push:
branches: branches:
- main - main
- pangea-merge
env:
# Setting an environment variable with the value of a configuration variable
WEB_APP_ENV: ${{ vars.WEB_APP_ENV }}
jobs: jobs:
deploy_web: deploy_web:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: staging
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: cat .github/workflows/versions.env >> $GITHUB_ENV - run: cat .github/workflows/versions.env >> $GITHUB_ENV
- run: echo "$WEB_APP_ENV" > .env
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
flutter-version: ${{ env.FLUTTER_VERSION }} flutter-version: ${{ env.FLUTTER_VERSION }}
@ -23,18 +26,20 @@ jobs:
- name: Prepare web - name: Prepare web
run: ./scripts/prepare-web.sh run: ./scripts/prepare-web.sh
- name: Build Release Web - name: Build Release Web
run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps --base-href "/client/" run: ./scripts/build-web.sh
- name: Build Website - name: Build Website
run: | run: |
mv build/web public mv build/web public
mv .env public/.env touch public/.env
echo "$WEB_APP_ENV" >> public/.env
cp public/.env public/assets/.env
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
with: with:
personal_token: ${{ secrets.PAGES_DEPLOY_TOKEN }} personal_token: ${{ secrets.PAGES_DEPLOY_TOKEN }}
publish_dir: ./public publish_dir: ./public
publish_branch: gh-pages publish_branch: gh-pages
# cname: app.staging.pangea.chat.im # cname: app.staging.pangea.chat
update_sentry: update_sentry:
runs-on: ubuntu-latest runs-on: ubuntu-latest

@ -3,8 +3,7 @@ name: Old Release Workflow
on: on:
push: push:
branches: branches:
- main - master-unused
- pangea-merge
concurrency: concurrency:
group: release_workflow group: release_workflow

@ -27,20 +27,20 @@ jobs:
- name: Build Release Web - name: Build Release Web
run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps
- name: Create archive - name: Create archive
run: tar -czf fluffychat-web.tar.gz build/web/ run: tar -czf pangeachat-web.tar.gz build/web/
- name: Upload Web Build - name: Upload Web Build
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: Web Build name: Web Build
path: fluffychat-web.tar.gz path: pangeachat-web.tar.gz
- name: Upload to release - name: Upload to release
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:
GITHUB_TOKEN: ${{ secrets.PAGES_DEPLOY_TOKEN }} GITHUB_TOKEN: ${{ secrets.PAGES_DEPLOY_TOKEN }}
with: with:
upload_url: ${{ github.event.release.upload_url }} upload_url: ${{ github.event.release.upload_url }}
asset_path: fluffychat-web.tar.gz asset_path: pangeachat-web.tar.gz
asset_name: fluffychat-web.tar.gz asset_name: pangeachat-web.tar.gz
asset_content_type: application/gzip asset_content_type: application/gzip
build_apk: build_apk:

1
.gitignore vendored

@ -12,6 +12,7 @@
.svn/ .svn/
prime prime
*.env *.env
!/public/.env
# libolm package # libolm package
/assets/js/package /assets/js/package

@ -3339,7 +3339,12 @@
"downloadTxtFile": "Download Text File", "downloadTxtFile": "Download Text File",
"downloadCSVFile": "Download CSV File", "downloadCSVFile": "Download CSV File",
"promotionalSubscriptionDesc": "You currently have a lifetime promotional subscription. Message support@pangea.chat for help changing your subscription.", "promotionalSubscriptionDesc": "You currently have a lifetime promotional subscription. Message support@pangea.chat for help changing your subscription.",
"originalSubscriptionPlatform": "Subscription purchased through", "originalSubscriptionPlatform": "Subscription purchased through {purchasePlatform}",
"@originalSubscriptionPlatform": {
"placeholders": {
"purchasePlatform": {}
}
},
"oneWeekTrial": "One Week Trial", "oneWeekTrial": "One Week Trial",
"creatingSpacePleaseWait": "Creating space. Please wait...", "creatingSpacePleaseWait": "Creating space. Please wait...",
"downloadXLSXFile": "Download Excel File", "downloadXLSXFile": "Download Excel File",
@ -3938,5 +3943,15 @@
"groupName": "Group name", "groupName": "Group name",
"createGroupAndInviteUsers": "Create a group and invite users", "createGroupAndInviteUsers": "Create a group and invite users",
"groupCanBeFoundViaSearch": "Group can be found via search", "groupCanBeFoundViaSearch": "Group can be found via search",
"inNoSpaces": "You are not a member of any classes or exchanges" "trialExpiration": "Your free trial expires on {expiration}",
"@trialExpiration": {
"placeholders": {
"expiration": {}
}
},
"freeTrialDesc": "New users recieve a one week free trial of Pangea Chat",
"activateTrial": "Activate Trial",
"inNoSpaces": "You are not a member of any classes or exchanges",
"successfullySubscribed": "You have successfully subscribed!",
"clickToManageSubscription": "Click here to manage your subscription."
} }

@ -1,9 +1,9 @@
{ {
"application_name": "FluffyChat", "application_name": "PangeaChat",
"application_welcome_message": null, "application_welcome_message": null,
"default_homeserver": "matrix.org", "default_homeserver": "matrix.pangea.chat",
"web_base_url": "https://fluffychat.im/web", "web_base_url": "https://web.pangea.chat",
"privacy_url": "https://fluffychat.im/en/privacy.html", "privacy_url": "https://pangea.chat/privacy",
"render_html": false, "render_html": false,
"hide_redacted_events": false, "hide_redacted_events": false,
"hide_unknown_events": false "hide_unknown_events": false

@ -1,8 +1,8 @@
import 'dart:ui'; import 'dart:ui';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/config/environment.dart';
// Project imports:
import 'package:matrix/matrix.dart';
abstract class AppConfig { abstract class AppConfig {
// #Pangea // #Pangea
@ -121,6 +121,7 @@ abstract class AppConfig {
"https://buy.stripe.com/test_bIY6ssd8z5Uz8ec8ww"; "https://buy.stripe.com/test_bIY6ssd8z5Uz8ec8ww";
static String iosPromoCode = static String iosPromoCode =
"https://apps.apple.com/redeem?ctx=offercodes&id=1445118630&code="; "https://apps.apple.com/redeem?ctx=offercodes&id=1445118630&code=";
static String trialSubscriptionId = "pangea_new_user_trial";
// Pangea# // Pangea#
static void loadFromJson(Map<String, dynamic> json) { static void loadFromJson(Map<String, dynamic> json) {

@ -1,10 +1,3 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pages/chat_details/participant_list_item.dart'; import 'package:fluffychat/pages/chat_details/participant_list_item.dart';
@ -26,6 +19,11 @@ import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
class ChatDetailsView extends StatelessWidget { class ChatDetailsView extends StatelessWidget {
final ChatDetailsController controller; final ChatDetailsController controller;
@ -546,8 +544,10 @@ class ChatDetailsView extends StatelessWidget {
backgroundColor: backgroundColor:
Theme.of(context).scaffoldBackgroundColor, Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor, foregroundColor: iconColor,
child: const Icon( child: Icon(
Icons.lock_outlined, room.locked
? Icons.lock_outlined
: Icons.no_encryption_outlined,
), ),
), ),
value: room.locked, value: room.locked,

@ -1,19 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; // Project imports:
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:uni_links/uni_links.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
@ -24,10 +14,23 @@ import 'package:fluffychat/pangea/utils/add_to_space.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/pangea/widgets/subscription/subscription_snackbar.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/client_stories_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/tor_stub.dart'
if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:uni_links/uni_links.dart';
import '../../../utils/account_bundles.dart'; import '../../../utils/account_bundles.dart';
import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart'; import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart';
import '../../utils/url_launcher.dart'; import '../../utils/url_launcher.dart';
@ -35,9 +38,6 @@ import '../../utils/voip/callkeep_manager.dart';
import '../../widgets/fluffy_chat_app.dart'; import '../../widgets/fluffy_chat_app.dart';
import '../../widgets/matrix.dart'; import '../../widgets/matrix.dart';
import 'package:fluffychat/utils/tor_stub.dart'
if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart';
enum SelectMode { enum SelectMode {
normal, normal,
share, share,
@ -445,6 +445,7 @@ class ChatListController extends State<ChatList>
//#Pangea //#Pangea
StreamSubscription? classStream; StreamSubscription? classStream;
StreamSubscription? _invitedSpaceSubscription; StreamSubscription? _invitedSpaceSubscription;
StreamSubscription? _subscriptionStatusStream;
//Pangea# //Pangea#
@override @override
@ -502,6 +503,14 @@ class ChatListController extends State<ChatList>
} }
} }
}); });
_subscriptionStatusStream = pangeaController
.subscriptionController.subscriptionStream.stream
.listen((event) {
if (mounted) {
showSubscribedSnackbar(context);
}
});
//Pangea# //Pangea#
super.initState(); super.initState();
@ -515,6 +524,7 @@ class ChatListController extends State<ChatList>
//#Pangea //#Pangea
classStream?.cancel(); classStream?.cancel();
_invitedSpaceSubscription?.cancel(); _invitedSpaceSubscription?.cancel();
_subscriptionStatusStream?.cancel();
//Pangea# //Pangea#
scrollController.removeListener(_onScroll); scrollController.removeListener(_onScroll);
super.dispose(); super.dispose();

@ -1,11 +1,10 @@
// Flutter imports: // Flutter imports:
import 'package:fluffychat/pangea/utils/password_forgotten.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/utils/password_forgotten.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
import 'login.dart'; import 'login.dart';
class LoginView extends StatelessWidget { class LoginView extends StatelessWidget {
@ -20,7 +19,30 @@ class LoginView extends StatelessWidget {
// enforceMobileMode: Matrix.of(context).client.isLogged(), // enforceMobileMode: Matrix.of(context).client.isLogged(),
// Pangea# // Pangea#
appBar: AppBar( appBar: AppBar(
leading: controller.loading ? null : const BackButton(), // #Pangea
// leading: controller.loading ? null : const BackButton(),
leading: controller.loading
? null
: Padding(
padding: const EdgeInsets.only(left: 10),
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor: MaterialStateProperty.all<Color>(
Theme.of(context)
.colorScheme
.background
.withOpacity(0.75),
),
shape: MaterialStateProperty.all<OutlinedBorder>(
const CircleBorder(),
),
),
child: const Icon(Icons.arrow_back),
),
),
// Pangea#
automaticallyImplyLeading: !controller.loading, automaticallyImplyLeading: !controller.loading,
centerTitle: true, centerTitle: true,
// #Pangea // #Pangea

@ -4,4 +4,8 @@ class PLocalKey {
static const String classes = 'classes'; static const String classes = 'classes';
static const String cachedClassCodeToJoin = "cachedclasscodetojoin"; static const String cachedClassCodeToJoin = "cachedclasscodetojoin";
static const String beganWebPayment = "beganWebPayment";
// making this a random string so that it's harder to guess
static const String activatedTrialKey = '7C4EuKIsph';
} }

@ -1,16 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:http/http.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/constants/local.key.dart';
import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/models/base_subscription_info.dart'; import 'package:fluffychat/pangea/models/base_subscription_info.dart';
@ -22,6 +15,12 @@ import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart';
import 'package:fluffychat/pangea/widgets/subscription/subscription_paywall.dart'; import 'package:fluffychat/pangea/widgets/subscription/subscription_paywall.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:http/http.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SubscriptionController extends BaseController { class SubscriptionController extends BaseController {
late PangeaController _pangeaController; late PangeaController _pangeaController;
@ -29,6 +28,7 @@ class SubscriptionController extends BaseController {
//convert this logic to use completer //convert this logic to use completer
bool initialized = false; bool initialized = false;
final StreamController subscriptionStream = StreamController.broadcast();
SubscriptionController(PangeaController pangeaController) : super() { SubscriptionController(PangeaController pangeaController) : super() {
_pangeaController = pangeaController; _pangeaController = pangeaController;
@ -59,13 +59,32 @@ class SubscriptionController extends BaseController {
: MobileSubscriptionInfo(pangeaController: _pangeaController); : MobileSubscriptionInfo(pangeaController: _pangeaController);
await subscription!.configure(); await subscription!.configure();
if (activatedNewUserTrial) {
setNewUserTrial();
}
initialized = true; initialized = true;
if (!kIsWeb) { if (!kIsWeb) {
Purchases.addCustomerInfoUpdateListener( Purchases.addCustomerInfoUpdateListener(
(CustomerInfo info) => updateCustomerInfo(), (CustomerInfo info) async {
final bool wasSubscribed = isSubscribed;
await updateCustomerInfo();
if (!wasSubscribed && isSubscribed) {
subscriptionStream.add(true);
}
},
); );
} else {
final bool? beganWebPayment = _pangeaController.pStoreService.read(
PLocalKey.beganWebPayment,
);
if (beganWebPayment ?? false) {
_pangeaController.pStoreService.delete(PLocalKey.beganWebPayment);
if (_pangeaController.subscriptionController.isSubscribed) {
subscriptionStream.add(true);
}
}
} }
setState(); setState();
} catch (e, s) { } catch (e, s) {
@ -74,6 +93,26 @@ class SubscriptionController extends BaseController {
} }
} }
bool get activatedNewUserTrial =>
_pangeaController.userController.inTrialWindow &&
(_pangeaController.pStoreService.read(PLocalKey.activatedTrialKey) ??
false);
void activateNewUserTrial() {
_pangeaController.pStoreService.save(PLocalKey.activatedTrialKey, true);
setNewUserTrial();
}
void setNewUserTrial() {
final String profileCreatedAt =
_pangeaController.userController.userModel!.profile!.createdAt;
final DateTime creationTimestamp = DateTime.parse(profileCreatedAt);
final DateTime expirationDate = creationTimestamp.add(
const Duration(days: 7),
);
subscription?.setTrial(expirationDate);
}
Future<void> updateCustomerInfo() async { Future<void> updateCustomerInfo() async {
if (subscription == null) { if (subscription == null) {
ErrorHandler.logError( ErrorHandler.logError(
@ -175,6 +214,10 @@ class SubscriptionController extends BaseController {
selectedSubscription.duration!, selectedSubscription.duration!,
isPromo: isPromo, isPromo: isPromo,
); );
_pangeaController.pStoreService.save(
PLocalKey.beganWebPayment,
true,
);
setState(); setState();
launchUrlString( launchUrlString(
paymentLink, paymentLink,

@ -2,14 +2,15 @@ import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'package:matrix/matrix.dart' as matrix;
import 'package:fluffychat/pangea/constants/language_keys.dart'; import 'package:fluffychat/pangea/constants/language_keys.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/widgets/fluffy_chat_app.dart'; import 'package:fluffychat/widgets/fluffy_chat_app.dart';
// Project imports:
import 'package:jwt_decode/jwt_decode.dart';
import 'package:matrix/matrix.dart' as matrix;
import '../constants/local.key.dart'; import '../constants/local.key.dart';
import '../models/user_model.dart'; import '../models/user_model.dart';
import '../repo/user_repo.dart'; import '../repo/user_repo.dart';
@ -109,6 +110,16 @@ class UserController extends BaseController {
} }
} }
bool get inTrialWindow {
final String? createdAt = userModel?.profile?.createdAt;
if (createdAt == null) {
return false;
}
return DateTime.parse(createdAt).isAfter(
DateTime.now().subtract(const Duration(days: 7)),
);
}
Future<bool> get areUserLanguagesSet async { Future<bool> get areUserLanguagesSet async {
try { try {
final PUserModel? toCheck = userModel ?? (await fetchUserModel()); final PUserModel? toCheck = userModel ?? (await fetchUserModel());

@ -1,3 +1,5 @@
// Project imports:
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/repo/subscription_repo.dart'; import 'package:fluffychat/pangea/repo/subscription_repo.dart';
@ -44,6 +46,9 @@ class SubscriptionInfo {
allProducts = await SubscriptionRepo.getAllProducts(); allProducts = await SubscriptionRepo.getAllProducts();
} }
bool get isNewUserTrial =>
currentSubscriptionId == AppConfig.trialSubscriptionId;
bool get currentSubscriptionIsPromotional => bool get currentSubscriptionIsPromotional =>
currentSubscriptionId?.startsWith("rc_promo") ?? false; currentSubscriptionId?.startsWith("rc_promo") ?? false;
@ -70,5 +75,16 @@ class SubscriptionInfo {
currentSubscriptionId = null; currentSubscriptionId = null;
} }
void setTrial(DateTime expiration) {
if (currentSubscription != null) return;
expirationDate = expiration;
currentSubscriptionId = AppConfig.trialSubscriptionId;
currentSubscription = SubscriptionDetails(
price: 0,
id: AppConfig.trialSubscriptionId,
periodType: 'trial',
);
}
Future<void> setCustomerInfo() async {} Future<void> setCustomerInfo() async {}
} }

@ -1,15 +1,15 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; // Package imports:
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/config/environment.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/models/base_subscription_info.dart'; import 'package:fluffychat/pangea/models/base_subscription_info.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart';
// Project imports:
import 'package:flutter/material.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class MobileSubscriptionInfo extends SubscriptionInfo { class MobileSubscriptionInfo extends SubscriptionInfo {
MobileSubscriptionInfo({required super.pangeaController}) : super(); MobileSubscriptionInfo({required super.pangeaController}) : super();
@ -182,7 +182,9 @@ class MobileSubscriptionInfo extends SubscriptionInfo {
); );
} else if (activeEntitlements.isEmpty) { } else if (activeEntitlements.isEmpty) {
debugPrint("User has no active entitlements"); debugPrint("User has no active entitlements");
resetSubscription(); if (!isNewUserTrial) {
resetSubscription();
}
return; return;
} }
final EntitlementInfo activeEntitlement = activeEntitlements[0]; final EntitlementInfo activeEntitlement = activeEntitlements[0];

@ -1,13 +1,13 @@
import 'dart:developer'; import 'dart:developer';
import 'package:fl_chart/fl_chart.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/pages/analytics/bar_chart_placeholder_data.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:fluffychat/pangea/pages/analytics/bar_chart_placeholder_data.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import '../../enum/time_span.dart'; import '../../enum/time_span.dart';
import '../../enum/use_type.dart'; import '../../enum/use_type.dart';
import '../../models/chart_analytics_model.dart'; import '../../models/chart_analytics_model.dart';
@ -102,39 +102,73 @@ class MessagesBarChartState extends State<MessagesBarChart> {
); );
} }
bool showLabelBasedOnTimeSpan(
TimeSpan timeSpan,
TimeSeriesInterval current,
TimeSeriesInterval? last,
int labelIndex,
) {
switch (timeSpan) {
case TimeSpan.day:
return current.end.hour % 3 == 0;
case TimeSpan.month:
if (current.end.month != last?.end.month) {
return true;
}
double width = MediaQuery.of(context).size.width;
if (FluffyThemes.isColumnMode(context)) {
width = width - FluffyThemes.navRailWidth - FluffyThemes.columnWidth;
}
const int numDays = 28;
const int minSpacePerDay = 20;
final int availableSpaces = width ~/ minSpacePerDay;
final int showAtInterval = (numDays / availableSpaces).floor() + 1;
final int lastDayOfCurrentMonth =
DateTime(current.end.year, current.end.month + 1, 0).day;
final bool isNextToMonth = labelIndex == 1 ||
current.end.day == 2 ||
current.end.day == lastDayOfCurrentMonth;
final bool shouldShowNextToMonth = showAtInterval <= 1;
return (current.end.day % showAtInterval == 0) &&
(!isNextToMonth || shouldShowNextToMonth);
case TimeSpan.week:
case TimeSpan.sixmonths:
case TimeSpan.year:
default:
return true;
}
}
String getLabelBasedOnTimeSpan( String getLabelBasedOnTimeSpan(
TimeSpan timeSpan, TimeSpan timeSpan,
TimeSeriesInterval current, TimeSeriesInterval current,
TimeSeriesInterval? last, TimeSeriesInterval? last,
int labelIndex, int labelIndex,
) { ) {
if (widget.chartAnalytics == null) { final bool showLabel = showLabelBasedOnTimeSpan(
timeSpan,
current,
last,
labelIndex,
);
if (widget.chartAnalytics == null || !showLabel) {
return ""; return "";
} }
if (isInSameGroup(last, current, timeSpan)) { if (isInSameGroup(last, current, timeSpan)) {
return "-"; return "-";
} }
switch (widget.chartAnalytics?.timeSpan ?? TimeSpan.month) { switch (widget.chartAnalytics?.timeSpan ?? TimeSpan.month) {
case TimeSpan.day: case TimeSpan.day:
return current.end.hour % 3 == 0 return DateFormat(DateFormat.HOUR).format(current.end);
? DateFormat(DateFormat.HOUR).format(current.end)
: "";
// return current.end.hour.toString();
case TimeSpan.week: case TimeSpan.week:
return DateFormat(DateFormat.ABBR_WEEKDAY).format(current.end); return DateFormat(DateFormat.ABBR_WEEKDAY).format(current.end);
case TimeSpan.month: case TimeSpan.month:
// return current.end.month != last?.end.month
// ? DateFormat(DateFormat.ABBR_MONTH_DAY).format(current.end)
// : current.end.day % 5 == 0 &&
// labelIndex != 1 &&
// current.end.day != 30
// ? DateFormat(DateFormat.DAY).format(current.end)
// : "'";
return current.end.month != last?.end.month return current.end.month != last?.end.month
? DateFormat(DateFormat.ABBR_MONTH).format(current.end) ? DateFormat(DateFormat.ABBR_MONTH).format(current.end)
: DateFormat(DateFormat.DAY).format(current.end); : DateFormat(DateFormat.DAY).format(current.end);
// return current.end.day.toString();
// text = DateFormat('DAY').format(timeSeriesIntervalStart);
case TimeSpan.sixmonths: case TimeSpan.sixmonths:
case TimeSpan.year: case TimeSpan.year:
return DateFormat(DateFormat.ABBR_STANDALONE_MONTH).format(current.end); return DateFormat(DateFormat.ABBR_STANDALONE_MONTH).format(current.end);

@ -1,16 +1,19 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; // Project imports:
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/config/environment.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/pages/settings_subscription/settings_subscription_view.dart'; import 'package:fluffychat/pangea/pages/settings_subscription/settings_subscription_view.dart';
import 'package:fluffychat/pangea/utils/subscription_app_id.dart'; import 'package:fluffychat/pangea/utils/subscription_app_id.dart';
import 'package:fluffychat/pangea/widgets/subscription/subscription_snackbar.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
// Project imports:
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
// Package imports:
import 'package:url_launcher/url_launcher_string.dart';
class SubscriptionManagement extends StatefulWidget { class SubscriptionManagement extends StatefulWidget {
const SubscriptionManagement({super.key}); const SubscriptionManagement({super.key});
@ -24,6 +27,7 @@ class SubscriptionManagementController extends State<SubscriptionManagement> {
final PangeaController pangeaController = MatrixState.pangeaController; final PangeaController pangeaController = MatrixState.pangeaController;
SubscriptionDetails? selectedSubscription; SubscriptionDetails? selectedSubscription;
late StreamSubscription _settingsSubscription; late StreamSubscription _settingsSubscription;
StreamSubscription? _subscriptionStatusStream;
@override @override
void initState() { void initState() {
@ -40,6 +44,7 @@ class SubscriptionManagementController extends State<SubscriptionManagement> {
void dispose() { void dispose() {
super.dispose(); super.dispose();
_settingsSubscription.cancel(); _settingsSubscription.cancel();
_subscriptionStatusStream?.cancel();
} }
bool get currentSubscriptionAvailable => bool get currentSubscriptionAvailable =>
@ -53,6 +58,10 @@ class SubscriptionManagementController extends State<SubscriptionManagement> {
?.currentSubscriptionIsPromotional ?? ?.currentSubscriptionIsPromotional ??
false; false;
bool get isNewUserTrial =>
pangeaController.subscriptionController.subscription?.isNewUserTrial ??
false;
bool get showManagementOptions { bool get showManagementOptions {
if (!currentSubscriptionAvailable) { if (!currentSubscriptionAvailable) {
return false; return false;
@ -116,6 +125,12 @@ class SubscriptionManagementController extends State<SubscriptionManagement> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_subscriptionStatusStream ??= pangeaController
.subscriptionController.subscriptionStream.stream
.listen((_) {
showSubscribedSnackbar(context);
context.go('/rooms');
});
return SettingsSubscriptionView(this); return SettingsSubscriptionView(this);
} }
} }

@ -1,14 +1,14 @@
import 'package:flutter/material.dart'; // Flutter imports:
// Project imports:
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:intl/intl.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/pages/settings_subscription/change_subscription.dart'; import 'package:fluffychat/pangea/pages/settings_subscription/change_subscription.dart';
import 'package:fluffychat/pangea/pages/settings_subscription/settings_subscription.dart'; import 'package:fluffychat/pangea/pages/settings_subscription/settings_subscription.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:intl/intl.dart';
class SettingsSubscriptionView extends StatelessWidget { class SettingsSubscriptionView extends StatelessWidget {
final SubscriptionManagementController controller; final SubscriptionManagementController controller;
@ -105,45 +105,44 @@ class ManagementNotAvailableWarning extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool currentSubscriptionAvailable = String getWarningText() {
controller.currentSubscriptionAvailable; final DateFormat formatter = DateFormat('yyyy-MM-dd');
final bool currentSubscriptionIsPromotional = if (controller.isNewUserTrial) {
controller.currentSubscriptionIsPromotional; return L10n.of(context)!.trialExpiration(
final String? purchasePlatformDisplayName = formatter.format(
controller.purchasePlatformDisplayName; subscriptionController.subscription!.expirationDate!,
final bool isLifetimeSubscription = ),
subscriptionController.subscription?.isLifetimeSubscription ?? false; );
final DateTime? expirationDate = }
subscriptionController.subscription?.expirationDate; if (controller.currentSubscriptionAvailable) {
String warningText = L10n.of(context)!.subsciptionPlatformTooltip;
String warningText = L10n.of(context)!.subscriptionManagementUnavailable; if (controller.purchasePlatformDisplayName != null) {
final DateFormat formatter = DateFormat('yyyy-MM-dd'); warningText +=
"\n${L10n.of(context)!.originalSubscriptionPlatform(controller.purchasePlatformDisplayName!)}";
if (currentSubscriptionAvailable) { }
warningText = L10n.of(context)!.subsciptionPlatformTooltip; return warningText;
} else if (currentSubscriptionIsPromotional) { }
if (isLifetimeSubscription) { if (controller.currentSubscriptionIsPromotional) {
warningText = L10n.of(context)!.promotionalSubscriptionDesc; if (subscriptionController.subscription?.isLifetimeSubscription ??
} else { false) {
warningText = L10n.of(context)!.promoSubscriptionExpirationDesc( return L10n.of(context)!.promotionalSubscriptionDesc;
formatter.format(expirationDate!), }
return L10n.of(context)!.promoSubscriptionExpirationDesc(
formatter.format(
subscriptionController.subscription!.expirationDate!,
),
); );
} }
return L10n.of(context)!.subscriptionManagementUnavailable;
} }
return Center( return Center(
child: Column( child: Padding(
children: [ padding: const EdgeInsets.all(20),
Text( child: Text(
warningText, getWarningText(),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
if (purchasePlatformDisplayName != null)
Text(
"${L10n.of(context)!.originalSubscriptionPlatform} $purchasePlatformDisplayName",
textAlign: TextAlign.center,
),
],
), ),
); );
} }

@ -1,15 +1,12 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import '../../../widgets/matrix.dart'; import '../../../widgets/matrix.dart';
import '../../utils/firebase_analytics.dart'; import '../../utils/firebase_analytics.dart';
import 'add_class_and_invite.dart'; import 'add_class_and_invite.dart';
@ -192,24 +189,49 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
), ),
), ),
), ),
if (parents.any((r) => r.room.id == possibleParent.id)) AnimatedSize(
SwitchListTile.adaptive( duration: const Duration(milliseconds: 300),
title: Text( curve: Curves.easeInOut,
L10n.of(context)!.suggestTo(possibleParentName), child: parents.any((r) => r.room.id == possibleParent.id)
style: TextStyle( ? SwitchListTile.adaptive(
color: Theme.of(context).colorScheme.secondary, title: Row(
), children: [
), const SizedBox(width: 32),
subtitle: Text( Text(
widget.mode == AddToClassMode.chat L10n.of(context)!.suggestTo(possibleParentName),
? L10n.of(context)!.suggestChatDesc(possibleParentName) style: TextStyle(
: L10n.of(context)!.suggestExchangeDesc(possibleParentName), color: Theme.of(context).colorScheme.secondary,
), ),
activeColor: AppConfig.activeToggleColor, ),
value: isSuggestedInSpace(possibleParent), ],
onChanged: (bool suggest) => ),
setSuggested(suggest, possibleParent), subtitle: Row(
), children: [
const SizedBox(width: 32),
Expanded(
child: Text(
widget.mode == AddToClassMode.chat
? L10n.of(context)!
.suggestChatDesc(possibleParentName)
: L10n.of(context)!.suggestExchangeDesc(
possibleParentName,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
activeColor: AppConfig.activeToggleColor,
value: isSuggestedInSpace(possibleParent),
onChanged: (bool suggest) =>
setSuggested(suggest, possibleParent),
)
: Container(),
),
Divider(
height: 0.5,
color: Theme.of(context).colorScheme.secondary.withAlpha(25),
),
], ],
), ),
); );
@ -223,7 +245,6 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
final String subtitle = widget.mode == AddToClassMode.exchange final String subtitle = widget.mode == AddToClassMode.exchange
? L10n.of(context)!.addToClassDesc ? L10n.of(context)!.addToClassDesc
: L10n.of(context)!.addToClassOrExchangeDesc; : L10n.of(context)!.addToClassOrExchangeDesc;
final scrollController = ScrollController();
return Column( return Column(
children: [ children: [
@ -253,27 +274,10 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
if (isOpen) ...[ if (isOpen) ...[
const Divider(height: 1), const Divider(height: 1),
possibleParents.isNotEmpty possibleParents.isNotEmpty
? Scrollbar( ? Column(
controller: scrollController, children: possibleParents
thumbVisibility: true, .mapIndexed((index, _) => getAddToSpaceToggleItem(index))
child: SingleChildScrollView( .toList(),
controller: scrollController,
child: Column(
children: [
const Divider(height: 1),
SizedBox(
height: min(possibleParents.length * 55, 500),
child: ListView.builder(
shrinkWrap: true,
itemCount: possibleParents.length,
itemBuilder: (BuildContext context, int i) {
return getAddToSpaceToggleItem(i);
},
),
),
],
),
),
) )
: Center( : Center(
child: Padding( child: Padding(

@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; // Flutter imports:
// Project imports:
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart';
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_gen/gen_l10n/l10n.dart';
class SubscriptionOptions extends StatelessWidget { class SubscriptionOptions extends StatelessWidget {
final PangeaController pangeaController; final PangeaController pangeaController;
@ -16,23 +17,18 @@ class SubscriptionOptions extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
child: ListView( child: Wrap(
children: [ alignment: WrapAlignment.center,
const SizedBox(height: 20), direction: Axis.horizontal,
Wrap( children: pangeaController
alignment: WrapAlignment.center, .subscriptionController.subscription!.availableSubscriptions
direction: Axis.horizontal, .map(
children: pangeaController (subscription) => SubscriptionCard(
.subscriptionController.subscription!.availableSubscriptions subscription: subscription,
.map( pangeaController: pangeaController,
(subscription) => SubscriptionCard( ),
subscription: subscription, )
pangeaController: pangeaController, .toList(),
),
)
.toList(),
),
],
), ),
); );
} }
@ -80,7 +76,11 @@ class SubscriptionCard extends StatelessWidget {
OutlinedButton( OutlinedButton(
onPressed: () { onPressed: () {
pangeaController.subscriptionController pangeaController.subscriptionController
.submitSubscriptionChange(subscription, context); .submitSubscriptionChange(
subscription,
context,
);
Navigator.of(context).pop();
}, },
child: Text(L10n.of(context)!.subscribe), child: Text(L10n.of(context)!.subscribe),
), ),

@ -1,9 +1,10 @@
import 'package:flutter/material.dart'; // Flutter imports:
// Project imports:
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/widgets/subscription/subscription_options.dart'; import 'package:fluffychat/pangea/widgets/subscription/subscription_options.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class SubscriptionPaywall extends StatelessWidget { class SubscriptionPaywall extends StatelessWidget {
final PangeaController pangeaController; final PangeaController pangeaController;
@ -28,7 +29,7 @@ class SubscriptionPaywall extends StatelessWidget {
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column( child: ListView(
children: [ children: [
if (pangeaController.matrixState.client.rooms.length > 1) ...[ if (pangeaController.matrixState.client.rooms.length > 1) ...[
Text( Text(
@ -44,12 +45,65 @@ class SubscriptionPaywall extends StatelessWidget {
style: const TextStyle(fontSize: 16), style: const TextStyle(fontSize: 16),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
SubscriptionOptions( pangeaController.userController.inTrialWindow
pangeaController: pangeaController, ? FreeTrialCard(
), pangeaController: pangeaController,
)
: SubscriptionOptions(
pangeaController: pangeaController,
),
], ],
), ),
), ),
); );
} }
} }
class FreeTrialCard extends StatelessWidget {
final PangeaController pangeaController;
const FreeTrialCard({super.key, required this.pangeaController});
@override
Widget build(BuildContext context) {
return Align(
child: Card(
shape: RoundedRectangleBorder(
side: BorderSide(
color: AppConfig.primaryColorLight.withAlpha(64),
),
borderRadius: const BorderRadius.all(Radius.zero),
),
child: SizedBox(
height: 250,
width: AppConfig.columnWidth * 0.75,
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
L10n.of(context)!.freeTrial,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24),
),
Text(
L10n.of(context)!.freeTrialDesc,
textAlign: TextAlign.center,
),
OutlinedButton(
onPressed: () {
pangeaController.subscriptionController
.activateNewUserTrial();
Navigator.of(context).pop();
},
child: Text(L10n.of(context)!.activateTrial),
),
],
),
),
),
),
);
}
}

@ -0,0 +1,34 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
void showSubscribedSnackbar(BuildContext context) {
final Widget text = RichText(
text: TextSpan(
children: [
TextSpan(
text: L10n.of(context)!.successfullySubscribed,
style: TextStyle(
color: Theme.of(context).brightness == Brightness.light
? Colors.white
: Colors.black,
),
),
const TextSpan(text: " "),
TextSpan(
text: L10n.of(context)!.clickToManageSubscription,
style: const TextStyle(color: AppConfig.primaryColor),
recognizer: TapGestureRecognizer()
..onTap = () => context.go('/rooms/settings/subscription'),
),
],
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: text,
),
);
}

@ -756,7 +756,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"bn": [ "bn": [
@ -1521,7 +1526,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"bo": [ "bo": [
@ -2286,7 +2296,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ca": [ "ca": [
@ -3046,7 +3061,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"cs": [ "cs": [
@ -3806,7 +3826,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"de": [ "de": [
@ -4566,7 +4591,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"el": [ "el": [
@ -5331,7 +5361,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"eo": [ "eo": [
@ -6091,7 +6126,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"es": [ "es": [
@ -6115,7 +6155,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"et": [ "et": [
@ -6875,7 +6920,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"eu": [ "eu": [
@ -7635,7 +7685,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"fa": [ "fa": [
@ -8395,7 +8450,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"fi": [ "fi": [
@ -9155,7 +9215,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"fr": [ "fr": [
@ -9915,7 +9980,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ga": [ "ga": [
@ -10675,7 +10745,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"gl": [ "gl": [
@ -11435,7 +11510,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"he": [ "he": [
@ -12195,7 +12275,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"hi": [ "hi": [
@ -12960,7 +13045,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"hr": [ "hr": [
@ -13720,7 +13810,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"hu": [ "hu": [
@ -14480,7 +14575,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"id": [ "id": [
@ -15240,7 +15340,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ie": [ "ie": [
@ -16002,7 +16107,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"it": [ "it": [
@ -16762,7 +16872,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ja": [ "ja": [
@ -17522,7 +17637,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ko": [ "ko": [
@ -18282,7 +18402,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"lt": [ "lt": [
@ -19042,7 +19167,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"lv": [ "lv": [
@ -19807,7 +19937,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"nb": [ "nb": [
@ -20567,7 +20702,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"nl": [ "nl": [
@ -21327,7 +21467,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"pl": [ "pl": [
@ -22087,7 +22232,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"pt": [ "pt": [
@ -22852,7 +23002,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"pt_BR": [ "pt_BR": [
@ -23612,7 +23767,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"pt_PT": [ "pt_PT": [
@ -24372,7 +24532,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ro": [ "ro": [
@ -25132,7 +25297,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ru": [ "ru": [
@ -25892,7 +26062,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"sk": [ "sk": [
@ -26653,7 +26828,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"sl": [ "sl": [
@ -27416,7 +27596,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"sr": [ "sr": [
@ -28176,7 +28361,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"sv": [ "sv": [
@ -28936,7 +29126,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"ta": [ "ta": [
@ -29701,7 +29896,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"th": [ "th": [
@ -30466,7 +30666,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"tr": [ "tr": [
@ -31226,7 +31431,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"uk": [ "uk": [
@ -31986,7 +32196,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"vi": [ "vi": [
@ -32749,7 +32964,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"zh": [ "zh": [
@ -33509,7 +33729,12 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
], ],
"zh_Hant": [ "zh_Hant": [
@ -34269,6 +34494,11 @@
"groupName", "groupName",
"createGroupAndInviteUsers", "createGroupAndInviteUsers",
"groupCanBeFoundViaSearch", "groupCanBeFoundViaSearch",
"inNoSpaces" "trialExpiration",
"freeTrialDesc",
"activateTrial",
"inNoSpaces",
"successfullySubscribed",
"clickToManageSubscription"
] ]
} }

@ -133,7 +133,6 @@ flutter:
generate: true generate: true
uses-material-design: true uses-material-design: true
assets: assets:
- .env
- assets/ - assets/
- assets/pangea/ - assets/pangea/
- assets/pangea/bot_faces/ - assets/pangea/bot_faces/

@ -2,4 +2,5 @@
flutter config --enable-web flutter config --enable-web
flutter clean flutter clean
flutter pub get flutter pub get
flutter build web --release --verbose --source-maps --dart-define=SENTRY_RELEASE=$CI_COMMIT_SHA flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --profile --source-maps --base-href "/client/"
# flutter build web --release --verbose --source-maps --dart-define=SENTRY_RELEASE=$CI_COMMIT_SHA

Loading…
Cancel
Save