diff --git a/.fvmrc b/.fvmrc index 090816fb..ce96236a 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.19.2" + "flutter": "3.19.3" } \ No newline at end of file diff --git a/.github/workflows/release_snapstore.workflow.yml b/.github/workflows/release_snapstore.workflow.yml index 2d5f2cd0..7fcdba51 100644 --- a/.github/workflows/release_snapstore.workflow.yml +++ b/.github/workflows/release_snapstore.workflow.yml @@ -5,9 +5,6 @@ on: push: branches: - release/snapstore - workflow_dispatch: - workflow_call: - jobs: build: runs-on: ubuntu-latest diff --git a/.vscode/common.code-snippets b/.vscode/common.code-snippets index 985fc497..60a78481 100644 --- a/.vscode/common.code-snippets +++ b/.vscode/common.code-snippets @@ -40,3 +40,5 @@ "body": "/// ********************************************\n/// * ${1:TITLE} END\n/// ********************************************" } } + + diff --git a/.vscode/settings.json b/.vscode/settings.json index 9b9a4e2a..156a77f4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,7 @@ 80 ] }, - "dart.flutterSdkPath": ".fvm/versions/3.19.2", + "dart.flutterSdkPath": ".fvm/versions/3.19.3", "search.exclude": { "**/.fvm": true }, diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8a64d0..20f07769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,15 @@ -## 3.20 (next) +## 3.21 (next) + +## 3.20 🧩 Puzzle update 🧩 - feat: app updates now visible in projects +- feat: new website launched!🚀 https://la.xsoulspace.dev Currently have only two pages, but now it fast, convenient and completely written with Dart with [Jaspr framework](https://github.com/schultek/jaspr) and [Tailwind](https://tailwindcss.com):) -- fix: characters limit layout made more compact and cozy +- fix: characters limit layout made more compact and cozy. +- fix: language switcher (didn't work after reloading). +- fix: vertical bar buttons adapted to fit languages. ## 3.19 diff --git a/configs/envs/prod.sample.json b/configs/envs/prod.sample.json index 26c887a8..e87700e7 100644 --- a/configs/envs/prod.sample.json +++ b/configs/envs/prod.sample.json @@ -4,5 +4,6 @@ "WIREDASH_PROJECT_ID": "", "WIREDASH_PROJECT_SECRET": "", "GOOGLE_CLIENT_ID": "", - "STORE": "snapstore" + "STORE": "snapstore", + "TELEGRAM_BOT_TOKEN": "" } diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 07394bb8..1397b728 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -1,8 +1,5 @@ -import 'dart:async'; - -import 'package:core/core.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:lastanswer/common_imports.dart'; import 'package:lastanswer/other/other.dart'; Future bootstrap({ diff --git a/lib/home/widgets/vertical_projects_bar.dart b/lib/home/widgets/vertical_projects_bar.dart index 42cca396..dbf6ecca 100644 --- a/lib/home/widgets/vertical_projects_bar.dart +++ b/lib/home/widgets/vertical_projects_bar.dart @@ -72,26 +72,25 @@ class BarItem extends StatelessWidget { @override Widget build(final BuildContext context) => GestureDetector( onTap: onTap, - child: SizedBox( - height: 56, - child: Stack( - children: [ - SizedBox( - height: 50, - child: child, - ), - Positioned( - bottom: 0, - right: 0, - left: 0, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 50, + child: child, + ), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 40), + child: FittedBox( + fit: BoxFit.fitWidth, child: Text( label, textAlign: TextAlign.center, style: context.textTheme.bodySmall, ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/settings/settings_screen.dart b/lib/settings/settings_screen.dart index b97e2f69..4163ecba 100644 --- a/lib/settings/settings_screen.dart +++ b/lib/settings/settings_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:lastanswer/_library/widgets/widgets.dart'; import 'package:lastanswer/common_imports.dart'; import 'package:lastanswer/other/feedback.dart'; @@ -13,7 +14,7 @@ class SettingsScreen extends HookWidget { void onBack() => Navigator.pop(context); final screenLayout = ScreenLayout.of(context); final isAccountViewVisible = - context.read().isAdSupported; + kDebugMode || context.read().isAdSupported; final appFeaturesNotifier = context.watch(); final tabsViews = [ const GeneralSettingsView(), diff --git a/lib/settings/views/supporter_view/supporter_view_web.dart b/lib/settings/views/supporter_view/supporter_view_web.dart index 3d94d8bd..ff2c7cd5 100644 --- a/lib/settings/views/supporter_view/supporter_view_web.dart +++ b/lib/settings/views/supporter_view/supporter_view_web.dart @@ -7,6 +7,7 @@ class SupportAppView extends StatelessWidget { @override Widget build(final BuildContext context) { final purhasesNotifier = context.watch(); + final adsNotifier = context.watch(); final purchasesAdsService = context.watch(); final isAdLoaded = purchasesAdsService.isLoaded; final l10n = context.l10n; @@ -15,14 +16,17 @@ class SupportAppView extends StatelessWidget { ? [ Text(l10n.adPleaseNote), const Gap(24), - FilledButton.tonal( - onPressed: isAdLoaded - ? () async => purhasesNotifier.watchAd(context) - : null, - child: isAdLoaded - ? Text(l10n.watchAd) - : const UiCircularProgress(), - ), + if (adsNotifier.isAllowedToWatchRewarded) + FilledButton.tonal( + onPressed: isAdLoaded + ? () async => purhasesNotifier.watchAd(context) + : null, + child: isAdLoaded + ? Text(l10n.watchAd) + : const UiCircularProgress(), + ) + else + const Text('Rewards ended. But, please come back tomorrow:)'), ] : [], ); diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 6cd83a18..762eea0b 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -8,149 +8,141 @@ PODS: - FlutterMacOS - file_selector_macos (0.0.1): - FlutterMacOS - - Firebase/Analytics (10.20.0): + - Firebase/Analytics (10.22.0): - Firebase/Core - - Firebase/Core (10.20.0): + - Firebase/Core (10.22.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 10.20.0) - - Firebase/CoreOnly (10.20.0): - - FirebaseCore (= 10.20.0) - - Firebase/Crashlytics (10.20.0): + - FirebaseAnalytics (~> 10.22.0) + - Firebase/CoreOnly (10.22.0): + - FirebaseCore (= 10.22.0) + - Firebase/Crashlytics (10.22.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 10.20.0) - - Firebase/Messaging (10.20.0): - - Firebase/CoreOnly - - FirebaseMessaging (~> 10.20.0) - - firebase_analytics (10.8.5): - - Firebase/Analytics (= 10.20.0) + - FirebaseCrashlytics (~> 10.22.0) + - firebase_analytics (10.8.8): + - Firebase/Analytics (= 10.22.0) - firebase_core - FlutterMacOS - - firebase_core (2.25.4): - - Firebase/CoreOnly (~> 10.20.0) - - FlutterMacOS - - firebase_crashlytics (3.4.14): - - Firebase/CoreOnly (~> 10.20.0) - - Firebase/Crashlytics (~> 10.20.0) - - firebase_core + - firebase_core (2.26.0): + - Firebase/CoreOnly (~> 10.22.0) - FlutterMacOS - - firebase_messaging (14.7.15): - - Firebase/CoreOnly (~> 10.20.0) - - Firebase/Messaging (~> 10.20.0) + - firebase_crashlytics (3.4.17): + - Firebase/CoreOnly (~> 10.22.0) + - Firebase/Crashlytics (~> 10.22.0) - firebase_core - FlutterMacOS - - FirebaseAnalytics (10.20.0): - - FirebaseAnalytics/AdIdSupport (= 10.20.0) + - FirebaseAnalytics (10.22.0): + - FirebaseAnalytics/AdIdSupport (= 10.22.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.20.0): + - nanopb (< 2.30911.0, >= 2.30908.0) + - FirebaseAnalytics/AdIdSupport (10.22.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.20.0) + - GoogleAppMeasurement (= 10.22.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseCore (10.20.0): + - nanopb (< 2.30911.0, >= 2.30908.0) + - FirebaseCore (10.22.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreExtension (10.21.0): + - FirebaseCoreExtension (10.22.0): - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.21.0): + - FirebaseCoreInternal (10.22.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseCrashlytics (10.20.0): + - FirebaseCrashlytics (10.22.0): - FirebaseCore (~> 10.5) - FirebaseInstallations (~> 10.0) - FirebaseSessions (~> 10.5) - GoogleDataTransport (~> 9.2) - GoogleUtilities/Environment (~> 7.8) - - nanopb (< 2.30910.0, >= 2.30908.0) + - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - - FirebaseInstallations (10.21.0): + - FirebaseInstallations (10.22.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseMessaging (10.20.0): - - FirebaseCore (~> 10.0) - - FirebaseInstallations (~> 10.0) - - GoogleDataTransport (~> 9.3) - - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - - GoogleUtilities/Environment (~> 7.8) - - GoogleUtilities/Reachability (~> 7.8) - - GoogleUtilities/UserDefaults (~> 7.8) - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseSessions (10.21.0): + - FirebaseSessions (10.22.0): - FirebaseCore (~> 10.5) - FirebaseCoreExtension (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleDataTransport (~> 9.2) - GoogleUtilities/Environment (~> 7.10) - - nanopb (< 2.30910.0, >= 2.30908.0) + - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesSwift (~> 2.1) - FlutterMacOS (1.0.0) - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - - GoogleAppMeasurement (10.20.0): - - GoogleAppMeasurement/AdIdSupport (= 10.20.0) + - FMDB (2.7.9): + - FMDB/standard (= 2.7.9) + - FMDB/standard (2.7.9) + - GoogleAppMeasurement (10.22.0): + - GoogleAppMeasurement/AdIdSupport (= 10.22.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.20.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.20.0) + - nanopb (< 2.30911.0, >= 2.30908.0) + - GoogleAppMeasurement/AdIdSupport (10.22.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.22.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.20.0): + - nanopb (< 2.30911.0, >= 2.30908.0) + - GoogleAppMeasurement/WithoutAdIdSupport (10.22.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleDataTransport (9.3.0): + - nanopb (< 2.30911.0, >= 2.30908.0) + - GoogleDataTransport (9.4.1): - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) + - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/AppDelegateSwizzler (7.12.0): + - GoogleUtilities/AppDelegateSwizzler (7.13.0): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - - GoogleUtilities/Environment (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (7.13.0): + - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.12.0): + - GoogleUtilities/Logger (7.13.0): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (7.13.0): - GoogleUtilities/Logger - - GoogleUtilities/Network (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Network (7.13.0): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.12.0)" - - GoogleUtilities/Reachability (7.12.0): + - "GoogleUtilities/NSData+zlib (7.13.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (7.13.0) + - GoogleUtilities/Reachability (7.13.0): - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (7.12.0): + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (7.13.0): - GoogleUtilities/Logger + - GoogleUtilities/Privacy - in_app_purchase_storekit (0.0.1): - Flutter - FlutterMacOS - isar_flutter_libs (1.0.0): - FlutterMacOS - - nanopb (2.30909.1): - - nanopb/decode (= 2.30909.1) - - nanopb/encode (= 2.30909.1) - - nanopb/decode (2.30909.1) - - nanopb/encode (2.30909.1) + - nanopb (2.30910.0): + - nanopb/decode (= 2.30910.0) + - nanopb/encode (= 2.30910.0) + - nanopb/decode (2.30910.0) + - nanopb/encode (2.30910.0) - package_info_plus (0.0.1): - FlutterMacOS - pasteboard (0.0.1): @@ -158,9 +150,9 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - PromisesObjC (2.3.1) - - PromisesSwift (2.3.1): - - PromisesObjC (= 2.3.1) + - PromisesObjC (2.4.0) + - PromisesSwift (2.4.0): + - PromisesObjC (= 2.4.0) - ReachabilitySwift (5.0.0) - share_plus (0.0.1): - FlutterMacOS @@ -181,7 +173,6 @@ DEPENDENCIES: - firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`) - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) - firebase_crashlytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos`) - - firebase_messaging (from `Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - in_app_purchase_storekit (from `Flutter/ephemeral/.symlinks/plugins/in_app_purchase_storekit/darwin`) - isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`) @@ -202,7 +193,6 @@ SPEC REPOS: - FirebaseCoreInternal - FirebaseCrashlytics - FirebaseInstallations - - FirebaseMessaging - FirebaseSessions - FMDB - GoogleAppMeasurement @@ -228,8 +218,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos firebase_crashlytics: :path: Flutter/ephemeral/.symlinks/plugins/firebase_crashlytics/macos - firebase_messaging: - :path: Flutter/ephemeral/.symlinks/plugins/firebase_messaging/macos FlutterMacOS: :path: Flutter/ephemeral in_app_purchase_storekit: @@ -256,32 +244,30 @@ SPEC CHECKSUMS: device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f file_saver: 44e6fbf666677faf097302460e214e977fdd977b file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 - Firebase: 10c8cb12fb7ad2ae0c09ffc86cd9c1ab392a0031 - firebase_analytics: 6637f2e65bef4f7c02dedb2c7c37a440b2a61784 - firebase_core: 2e1a33fd13fb581f0dc809c18be25cdc1a2e10db - firebase_crashlytics: c8d9eb8c32ad6248e9be271d467d85b96da98fe9 - firebase_messaging: aa7d68aa238b24ee36bfe33f7a73561d3a78b069 - FirebaseAnalytics: a2731bf3670747ce8f65368b118d18aa8e368246 - FirebaseCore: 28045c1560a2600d284b9c45a904fe322dc890b6 - FirebaseCoreExtension: 1c044fd46e95036cccb29134757c499613f3f564 - FirebaseCoreInternal: 43c1788eaeee9d1b97caaa751af567ce11010d00 - FirebaseCrashlytics: 81530595edb6d99f1918f723a6c33766a24a4c86 - FirebaseInstallations: 390ea1d10a4d02b20c965cbfd527ee9b3b412acb - FirebaseMessaging: 06c414a21b122396a26847c523d5c370f8325df5 - FirebaseSessions: 80c2bbdd28166267b3d132debe5f7531efdb00bc + Firebase: 797fd7297b7e1be954432743a0b3f90038e45a71 + firebase_analytics: 2791af000dcb7187e8d0d7324dce085e40de8938 + firebase_core: 2dd24517c818d09543dffe1b74daa16fb3f55a81 + firebase_crashlytics: 8225e7209241bda48e95416f2a345adcec855114 + FirebaseAnalytics: 8d0ff929c63b7f72260f332b86ccf569776b75d3 + FirebaseCore: 0326ec9b05fbed8f8716cddbf0e36894a13837f7 + FirebaseCoreExtension: 6394c00b887d0bebadbc7049c464aa0cbddc5d41 + FirebaseCoreInternal: bca337352024b18424a61e478460547d46c4c753 + FirebaseCrashlytics: e568d68ce89117c80cddb04073ab9018725fbb8c + FirebaseInstallations: 763814908793c0da14c18b3dcffdec71e29ed55e + FirebaseSessions: cd97fb07674f3906619c871eefbd260a1546c9d3 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - GoogleAppMeasurement: bb3c564c3efb933136af0e94899e0a46167466a8 - GoogleDataTransport: 57c22343ab29bc686febbf7cbb13bad167c2d8fe - GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34 + FMDB: aa44149f6fb634b1ac54f64f47064bb0d0c5a032 + GoogleAppMeasurement: ccefe3eac9b0aa27f96066809fb1a7fe4b462626 + GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a + GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 in_app_purchase_storekit: 4fb7ee9e824b1f09107fbfbbce8c4b276366dc43 isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a - nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 + nanopb: 438bc412db1928dac798aa6fd75726007be04262 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 - PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 diff --git a/packages/core/assets/json/updates_notifications.json b/packages/core/assets/json/updates_notifications.json index 6cd9642b..b202548c 100644 --- a/packages/core/assets/json/updates_notifications.json +++ b/packages/core/assets/json/updates_notifications.json @@ -1,4 +1,21 @@ [ + { + "id": "6", + "message": { + "ru": "- feat: теперь обновления приложения видны в проектах\n- feat: запущен новый сайт!🚀 https://la.xsoulspace.dev На данный момент у него всего два страницы, но теперь он быстрый, удобный и полностью написан на Dart с использованием [Jaspr framework](https://github.com/schultek/jaspr) и [Tailwind](https://tailwindcss.com):)\n- fix: layout с ограничением количества символов для соцсетей стал более компактным и приятным.\n- fix: переключатель языка (работал только после перезагрузки).\n- fix: кнопки вертикального бара адаптированы под языки.", + "en": "- fix: characters limit layout made more compact and cozy.\n- fix: language switcher (didn't work after reloading).\n- fix: vertical bar buttons adapted to fit languages.\n- feat: app updates now visible in projects\n- feat: new website launched!🚀 https://la.xsoulspace.dev Currently have only two pages, but now it fast, convenient and completely written with Dart with [Jaspr framework](https://github.com/schultek/jaspr) and [Tailwind](https://tailwindcss.com):)\n- fix: characters limit layout made more compact and cozy.\n- fix: language switcher (didn't work after reloading).\n- fix: vertical bar buttons adapted to fit languages.", + "it": "- feat: ora gli aggiornamenti dell'app sono visibili nei progetti\n- feat: è stato lanciato un nuovo sito!🚀 https://la.xsoulspace.dev Al momento ha solo due pagine, ma adesso è veloce, pratico e completamente scritto in Dart con [Jaspr framework](https://github.com/schultek/jaspr) e [Tailwind](https://tailwindcss.com):)\n- fix: il layout con limite del numero di caratteri per le reti sociali è diventato più compatto e accogliente.\n- fix: il selettore di lingua (funzionava solo dopo il ricaricamento).\n- fix: i pulsanti della barra verticale sono stati adattati alle lingue.", + "ga": "" + }, + "title": { + "ru": "🧩 Обновление Пазл 🧩", + "en": "🧩 Puzzle update 🧩", + "it": "🧩 Aggiornamento Puzzle 🧩", + "ga": "🧩 Puzzle update 🧩" + }, + "created": "2024-03-14" + }, + { "id": "5", "message": { diff --git a/packages/core/lib/envs.dart b/packages/core/lib/envs.dart index c0778b91..7f6b0ab5 100644 --- a/packages/core/lib/envs.dart +++ b/packages/core/lib/envs.dart @@ -22,6 +22,7 @@ class Envs { static const wiredashProjectSecret = String.fromEnvironment('WIREDASH_PROJECT_SECRET'); static final store = StoreType.fromEnv(); + static const telegramBotToken = String.fromEnvironment('TELEGRAM_BOT_TOKEN'); } enum StoreType { diff --git a/packages/core/lib/src/data_repositories/ads_repository.dart b/packages/core/lib/src/data_repositories/ads_repository.dart new file mode 100644 index 00000000..f8b3f344 --- /dev/null +++ b/packages/core/lib/src/data_repositories/ads_repository.dart @@ -0,0 +1,15 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_models/shared_models.dart'; + +import '../../core.dart'; + +class AdsRepository { + AdsRepository(final BuildContext context) + : _local = AdsLocalDataSourceImpl( + localDb: context.read(), + ); + final AdsLocalDataSource _local; + Future saveState(final AdsStateModel ads) => _local.saveState(ads); + Future getState() => _local.getState(); +} diff --git a/packages/core/lib/src/data_repositories/data_repositories.dart b/packages/core/lib/src/data_repositories/data_repositories.dart index 685f42ed..249d589d 100644 --- a/packages/core/lib/src/data_repositories/data_repositories.dart +++ b/packages/core/lib/src/data_repositories/data_repositories.dart @@ -1,3 +1,4 @@ +export 'ads_repository.dart'; export 'emoji_repository.dart'; export 'interfaces/interfaces.dart'; export 'notifications_repository.dart'; diff --git a/packages/core/lib/src/data_sources/interfaces/ads.dart b/packages/core/lib/src/data_sources/interfaces/ads.dart new file mode 100644 index 00000000..99e5b7d7 --- /dev/null +++ b/packages/core/lib/src/data_sources/interfaces/ads.dart @@ -0,0 +1,6 @@ +import 'package:shared_models/shared_models.dart'; + +abstract interface class AdsLocalDataSource { + Future saveState(final AdsStateModel ads); + Future getState(); +} diff --git a/packages/core/lib/src/data_sources/interfaces/interfaces.dart b/packages/core/lib/src/data_sources/interfaces/interfaces.dart index 4a9660bc..8f0a9c49 100644 --- a/packages/core/lib/src/data_sources/interfaces/interfaces.dart +++ b/packages/core/lib/src/data_sources/interfaces/interfaces.dart @@ -1,3 +1,4 @@ +export 'ads.dart'; export 'auth.dart'; export 'complex_local_db.dart'; export 'dictionaries.dart'; diff --git a/packages/core/lib/src/data_sources/local/ads.dart b/packages/core/lib/src/data_sources/local/ads.dart new file mode 100644 index 00000000..f5eb83dc --- /dev/null +++ b/packages/core/lib/src/data_sources/local/ads.dart @@ -0,0 +1,26 @@ +import 'package:shared_models/shared_models.dart'; + +import '../data_sources.dart'; + +class AdsLocalDataSourceImpl implements AdsLocalDataSource { + AdsLocalDataSourceImpl({required this.localDb}); + final LocalDbDataSource localDb; + + @override + Future saveState(final AdsStateModel ads) async { + localDb.setItem( + key: SharedPreferencesKeys.adsState.name, + value: ads, + convertToJson: (final v) => v.toJson(), + ); + } + + @override + Future getState() async { + final item = localDb.getItem( + key: SharedPreferencesKeys.adsState.name, + convertFromJson: AdsStateModel.fromJson, + ); + return item ?? const AdsStateModel(); + } +} diff --git a/packages/core/lib/src/data_sources/local/local.dart b/packages/core/lib/src/data_sources/local/local.dart index 444bd3cf..1f662a58 100644 --- a/packages/core/lib/src/data_sources/local/local.dart +++ b/packages/core/lib/src/data_sources/local/local.dart @@ -1,3 +1,4 @@ +export 'ads.dart'; export 'dictionaries.dart'; export 'emoji.dart'; export 'last_used_emoji.dart'; diff --git a/packages/core/lib/src/data_sources/local/shared_preferences_keys.dart b/packages/core/lib/src/data_sources/local/shared_preferences_keys.dart index fd872e2d..39e1a925 100644 --- a/packages/core/lib/src/data_sources/local/shared_preferences_keys.dart +++ b/packages/core/lib/src/data_sources/local/shared_preferences_keys.dart @@ -14,4 +14,5 @@ enum SharedPreferencesKeys { usageDayCount, webProjects, tags, + adsState, } diff --git a/packages/core/lib/src/foundation/extensions/value_notifier.dart b/packages/core/lib/src/foundation/extensions/value_notifier.dart index cfcfd439..12e6dfc2 100644 --- a/packages/core/lib/src/foundation/extensions/value_notifier.dart +++ b/packages/core/lib/src/foundation/extensions/value_notifier.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:shared_models/shared_models.dart'; extension ValueNotifierExtension on ValueNotifier { // ignore: use_setters_to_change_properties @@ -6,3 +7,15 @@ extension ValueNotifierExtension on ValueNotifier { value = state; } } + +class LoadableStateNotifier extends ValueNotifier> { + LoadableStateNotifier(final T value) : super(LoadableContainer(value: value)); + bool get isLoading => value.isLoading; + T get state => value.value; + set state(final T v) => setState(v); + // ignore: avoid_positional_boolean_parameters + void setIsLoading(final bool isLoading) => + value = value.copyWith(isLoaded: !isLoading); + void setState(final T state) => + value = value.copyWith(isLoaded: true, value: state); +} diff --git a/packages/core/lib/src/services/services.dart b/packages/core/lib/src/services/services.dart index 8d5cf3ff..b3c2e018 100644 --- a/packages/core/lib/src/services/services.dart +++ b/packages/core/lib/src/services/services.dart @@ -4,3 +4,4 @@ export 'file_service/file_service.dart'; export 'purchases_ads/purchases_ads.dart'; export 'purchases_iap/purchases_iap_service.dart'; export 'share_service.dart'; +export 'telegram/bot_telegram_service.dart'; diff --git a/packages/core/lib/src/services/telegram/bot_telegram_service.dart b/packages/core/lib/src/services/telegram/bot_telegram_service.dart new file mode 100644 index 00000000..4f17bfa7 --- /dev/null +++ b/packages/core/lib/src/services/telegram/bot_telegram_service.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:shared_models/shared_models.dart'; +import 'package:televerse/televerse.dart'; + +import '../../../core.dart'; + +class BotTelegramService with ChangeNotifier implements Loadable { + final _bot = Bot(Envs.telegramBotToken); + bool get _isInitializable => Envs.telegramBotToken.isNotEmpty; + @override + Future onLoad() async { + if (!_isInitializable) return; + await _bot.start(); + } + + Future initializeMenu(final BuildContext context) async { + final l10n = context.l10n; + } + + @override + void dispose() { + unawaited(_bot.stop()); + super.dispose(); + } +} diff --git a/packages/core/lib/src/services/telegram/telegram.dart b/packages/core/lib/src/services/telegram/telegram.dart new file mode 100644 index 00000000..79fbfb15 --- /dev/null +++ b/packages/core/lib/src/services/telegram/telegram.dart @@ -0,0 +1 @@ +export 'bot_telegram_service.dart'; diff --git a/packages/core/lib/src/state/ads_notifier.dart b/packages/core/lib/src/state/ads_notifier.dart new file mode 100644 index 00000000..bf1a37ca --- /dev/null +++ b/packages/core/lib/src/state/ads_notifier.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_models/shared_models.dart'; + +import '../../core.dart'; +import '../data_repositories/ads_repository.dart'; + +class AdsNotifier extends LoadableStateNotifier { + AdsNotifier(final BuildContext context) + : _adsRepository = context.read(), + super(const AdsStateModel()); + final AdsRepository _adsRepository; + + Future onLoad() async { + state = await _adsRepository.getState(); + } + + bool get isAllowedToWatchRewarded => + todayDate == state.lastDateWhenAdRewardReceived; + + Future onAwareded() async { + final updatedState = + state.copyWith(lastDateWhenAdRewardReceived: todayDate); + await _adsRepository.saveState(updatedState); + } +} diff --git a/packages/core/lib/src/state/logic/locale_logic.dart b/packages/core/lib/src/state/logic/locale_logic.dart index a827d8e6..1b2998ff 100644 --- a/packages/core/lib/src/state/logic/locale_logic.dart +++ b/packages/core/lib/src/state/logic/locale_logic.dart @@ -27,7 +27,8 @@ class LocaleLogic { required final Locale? oldLocale, required final Locale uiLocale, }) async { - final didChanged = oldLocale?.languageCode != newLocale?.languageCode; + final didChanged = oldLocale?.languageCode != newLocale?.languageCode || + uiLocale != newLocale; if (!didChanged) return null; Locale? updatedLocale = oldLocale; diff --git a/packages/core/lib/src/state/purchases_notifier.dart b/packages/core/lib/src/state/purchases_notifier.dart index 872577a0..10ee1dac 100644 --- a/packages/core/lib/src/state/purchases_notifier.dart +++ b/packages/core/lib/src/state/purchases_notifier.dart @@ -10,29 +10,28 @@ class PurchasesNotifierDto { final PurchasesAdsService purchasesAdsService; } -class PurchasesNotifier - extends ValueNotifier> { +class PurchasesNotifier extends LoadableStateNotifier { PurchasesNotifier(final BuildContext context) : dto = PurchasesNotifierDto(context), - super(const LoadableContainer(value: PurchasesModel.empty)); + super(PurchasesModel.empty); final PurchasesNotifierDto dto; Future onLocalUserLoad() async { final localPurchases = await dto.purchasesRepository.getLocalPurchases(); - _emitLoaded(localPurchases); + state = localPurchases; unawaited(recordNewDay()); } Future onRemoteUserLoad() async { final purchases = await dto.purchasesRepository .mergePurchases(localPurchases: value.value); - _emitLoaded(purchases); + state = purchases; } Future recordNewDay() async { final purchases = await dto.purchasesRepository.recordNewDay(); - _emitLoaded(purchases); + state = purchases; } bool get isActive => value.value.isActive; @@ -54,6 +53,7 @@ class PurchasesNotifier final toasts = Toasts.of(context); final l10n = context.l10n; final userNotifier = context.read(); + final adsNotifier = context.read(); final isDark = userNotifier.settings.themeMode == ThemeMode.dark; final adInstance = await dto.purchasesAdsService.prepareAdInstance( @@ -67,15 +67,14 @@ class PurchasesNotifier if (reward.isRewarded) { final updatedPurchases = await dto.purchasesRepository.receiveAdVideoReward(); - _emitLoaded(updatedPurchases); + state = updatedPurchases; unawaited( - toasts.showBottomToast(message: l10n.rewardForAdThankYou(7)), + Future.wait([ + adsNotifier.onAwareded(), + toasts.showBottomToast(message: l10n.rewardForAdThankYou(7)), + ]), ); } adInstance.dispose(); } - - void _emitLoaded(final PurchasesModel state) { - value = LoadableContainer.loaded(state); - } } diff --git a/packages/core/lib/src/state/state.dart b/packages/core/lib/src/state/state.dart index df79c8fc..a1ad0606 100644 --- a/packages/core/lib/src/state/state.dart +++ b/packages/core/lib/src/state/state.dart @@ -17,6 +17,7 @@ import '../services/file_service/file_service_i.dart'; import 'logic/logic.dart'; import 'user_remote_initializer.dart'; +export 'ads_notifier.dart'; export 'tags_notifier.dart'; part 'app_notifier.dart'; diff --git a/packages/core/lib/src/state/user_notifier.dart b/packages/core/lib/src/state/user_notifier.dart index d980e79b..59e30c8b 100644 --- a/packages/core/lib/src/state/user_notifier.dart +++ b/packages/core/lib/src/state/user_notifier.dart @@ -65,6 +65,7 @@ class UserNotifier extends ValueNotifier> { required final RemoteUserInitializer remote, }) async { value = LoadableContainer.loaded(await dto.userRepository.getLocalUser()); + unawaited(updateLocale(value.value.settings.locale)); unawaited(local.onUserLoad()); if (dto.appFeaturesNotifier.value.isRemoteServicesEnabled) { await loadRemoteUser(); diff --git a/packages/core/lib/src/state_di/global_states_provider.dart b/packages/core/lib/src/state_di/global_states_provider.dart index b5f10845..e4410932 100644 --- a/packages/core/lib/src/state_di/global_states_provider.dart +++ b/packages/core/lib/src/state_di/global_states_provider.dart @@ -42,6 +42,7 @@ class GlobalStatesProvider extends StatelessWidget { Provider(create: UserRepository.new), Provider(create: NotificationsRepository.new), Provider(create: ProjectsRepository.new), + Provider(create: AdsRepository.new), /// notifiers & blocs ChangeNotifierProvider(create: EmojiStateNotifier.new), @@ -49,6 +50,7 @@ class GlobalStatesProvider extends StatelessWidget { ChangeNotifierProvider(create: SpecialEmojiStateNotifier.new), ChangeNotifierProvider(create: NotificationsNotifier.new), ChangeNotifierProvider(create: TagsNotifier.new), + ChangeNotifierProvider(create: AdsNotifier.new), ChangeNotifierProvider(create: ProjectsNotifier.new), ChangeNotifierProvider(create: PurchasesNotifier.new), ChangeNotifierProvider(create: UserNotifier.new), diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml index a96a0af8..c3d3963d 100644 --- a/packages/core/pubspec.yaml +++ b/packages/core/pubspec.yaml @@ -52,14 +52,15 @@ dependencies: provider: ^6.1.1 quiver: ^3.2.1 rxdart: ^0.27.7 - serverpod_auth_google_flutter: 1.2.3 - serverpod_auth_shared_flutter: 1.2.3 - serverpod_client: 1.2.3 - serverpod_flutter: 1.2.3 + serverpod_auth_google_flutter: 1.2.5 + serverpod_auth_shared_flutter: 1.2.5 + serverpod_client: 1.2.5 + serverpod_flutter: 1.2.5 share_plus: ^7.2.1 shared_models: path: ../shared_models shared_preferences: ^2.2.2 + televerse: ^1.13.2 ui_kit: path: ../ui_kit universal_io: ^2.2.0 diff --git a/packages/core_server_client/pubspec.yaml b/packages/core_server_client/pubspec.yaml index d4df5095..0a301856 100644 --- a/packages/core_server_client/pubspec.yaml +++ b/packages/core_server_client/pubspec.yaml @@ -8,8 +8,8 @@ environment: dependencies: core: path: ../core - serverpod_auth_client: 1.2.3 - serverpod_client: 1.2.3 + serverpod_auth_client: 1.2.5 + serverpod_client: 1.2.5 shared_models: path: ../shared_models diff --git a/packages/landing/.gitignore b/packages/landing/.gitignore new file mode 100644 index 00000000..3c8a1572 --- /dev/null +++ b/packages/landing/.gitignore @@ -0,0 +1,6 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build output. +build/ diff --git a/packages/landing/.vscode/common.code-snippets b/packages/landing/.vscode/common.code-snippets new file mode 100644 index 00000000..5bb3f0cd --- /dev/null +++ b/packages/landing/.vscode/common.code-snippets @@ -0,0 +1,55 @@ +{ + // Place workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + "todo(username):description": { + "prefix": "todo", + "body": "// TODO(${1:username}): ${2:description}, " + }, + "todo(arenukvern):description": { + "prefix": "tdar", + "body": "// TODO(arenukvern): ${1:description}, " + }, + "fixme(username):description": { + "prefix": "fixme", + "body": "// FIXME(${1:username}): ${2:description}, " + }, + "unimplemented": { + "prefix": "unimplemented", + "body": "// TODO(${1:username}): ${2:unimplemented}\nthrow UnimplementedError('${3:unimplemented error}');" + }, + "section-start": { + "prefix": "section start", + "body": "/// ********************************************\n/// * ${1:TITLE} START\n/// ********************************************" + }, + "section-end": { + "prefix": "section end", + "body": "/// ********************************************\n/// * ${1:TITLE} END\n/// ********************************************" + }, + "stateless-component": { + "prefix": "stlc", + "body": [ + "class $1 extends StatelessComponent {", + " const $1({super.key});", + " @override", + " Iterable build(BuildContext context) sync* {", + " yield throw UnimplementedError();", + " }", + "}" + ], + "description": "Create a new StatelessComponent class" + } +} diff --git a/packages/landing/.vscode/launch.json b/packages/landing/.vscode/launch.json new file mode 100644 index 00000000..06db6dd1 --- /dev/null +++ b/packages/landing/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "program": "web/main.dart", + "name": "debug serve", + "request": "attach", + "type": "dart" + // "args": ["jaspr", "serve"] + // "console": "debugConsole" + // "internalConsoleOptions": "neverOpen" + } + ] +} diff --git a/packages/landing/Makefile b/packages/landing/Makefile new file mode 100644 index 00000000..f15cc4f1 --- /dev/null +++ b/packages/landing/Makefile @@ -0,0 +1,11 @@ +# https://tailwindcss.com/blog/standalone-cli +# update config: ./tailwindcss init +update-tailwind: + curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64 + chmod +x tailwindcss-macos-arm64 + mv tailwindcss-macos-arm64 ./tailwindcss + +watch-tailwind: + ./tailwindcss -i ./web/styles.css -o ./build/output.css --watch +compile-tailwind: + ./tailwindcss -i ./web/styles.css -o ./build/jaspr/output.css --minify diff --git a/packages/landing/README.md b/packages/landing/README.md new file mode 100644 index 00000000..e1d686ca --- /dev/null +++ b/packages/landing/README.md @@ -0,0 +1,3 @@ +# landing + +A pure client-side app without ssr. diff --git a/packages/landing/analysis_options.yaml b/packages/landing/analysis_options.yaml new file mode 100644 index 00000000..dee8927a --- /dev/null +++ b/packages/landing/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/landing/lib/app.dart b/packages/landing/lib/app.dart new file mode 100644 index 00000000..e1981d92 --- /dev/null +++ b/packages/landing/lib/app.dart @@ -0,0 +1,244 @@ +import 'package:jaspr/jaspr.dart'; +import 'package:jaspr_router/jaspr_router.dart'; + +import 'sections/sections.dart'; +import 'ui_kit/ui_kit.dart'; + +/// Main colors: +/// Emrald, stone, +/// The root component for this app. +class App extends StatelessComponent { + @override + Iterable build(BuildContext context) sync* { + yield Router( + routes: [ + ShellRoute( + builder: (context, state, child) => AppNavigator(child: child), + routes: [ + // Route( + // path: RoutePaths.home, + // builder: (context, state) => DownloadScreen(), + // ), + // FIXME(arenukvern): disabled for debugging purposes, + Route( + path: RoutePaths.home, + name: RoutePaths.home, + builder: (context, state) => HomeScreen(), + ), + Route( + path: RoutePaths.download, + name: RoutePaths.download, + title: 'Last Answer: download', + builder: (context, state) => DownloadScreen(), + ), + ]), + ], + ); + } +} + +class RoutePaths { + static const download = '/download'; + static const home = '/'; +} + +extension BuildContextX on BuildContext { + RouterState get router => Router.of(this); +} + +class AppNavigator extends StatelessComponent { + const AppNavigator({required this.child, super.key}); + final Component child; + @override + Iterable build(BuildContext context) sync* { + yield body( + [ + div([ + HeaderAppBar(), + div([child], classes: 'overflow-x-hidden'), + FotterBottomBar(), + ], classes: 'flex min-h-screen flex-col justify-between') + ], + classes: 'antialiased text-stone-700 bg-amber-50/10', + ); + } +} + +class HeaderAppBar extends StatelessComponent { + const HeaderAppBar({super.key}); + @override + Iterable build(BuildContext context) sync* { + yield header( + [ + div([ + div([ + TwSpacer.x('4'), + LinkButton( + liDecoration: LiLinkButtonDecoration.stone900 + .copyWith(textStyle: 'text-xl font-semibold'), + titleText: 'Last Answer', + url: RoutePaths.home, + ), + TwSpacer.x('4'), + div([ + nav([ + ul([ + ...[ + (title: 'Download', url: RoutePaths.download), + // (title: 'Sync', url: '/sync'), + // (title: 'Pricing', url: '/pricing'), + // (title: 'Community', url: '/community'), + ].map( + (e) => LiLinkButton( + item: e, + decoration: LiLinkButtonDecoration.emeraldNormal), + ), + ], classes: 'flex space-x-8'), + ], classes: 'text-sm leading-6 font-semibold') + // FIXME(arenukvern): uncomment when menu is ready, + // ], classes: 'relative hidden md:flex items-center ml-auto'), + ], classes: 'relative flex items-center ml-auto'), + // AppBarBurgerButton(), + TwSpacer.x('4'), + OpenAppButton( + title: 'Open app', + decoration: StyledButtonDecoration.filled, + ), + TwSpacer.x('4') + ], classes: 'relative flex items-center'), + ], + classes: + 'bg-white/70 backdrop-blur-md transition max-w-8xl mx-auto py-2 ' + 'border-b border-stone-900/5'), + ], + classes: 'sticky top-0 z-50', + ); + } +} + +final _bottomLinks = [ + ( + sectionTitle: 'Product', + links: [ + (title: 'Download', url: RoutePaths.download), + ( + title: 'Changelog', + // TODO(arenukvern): add page, + url: + 'https://github.com/xsoulspace/last_answer/blob/master/CHANGELOG.md' + ), + // (title: 'Features', url: '/features'), + // (title: 'Sync', url: '/sync'), + // (title: 'Pricing', url: '/pricing'), + ] + ), + ( + sectionTitle: 'Community', + links: [ + // (title: 'About', url: '/community'), + (title: 'Discord (en)', url: 'https://discord.com/invite/y54DpJwmAn'), + (title: 'Telegram (ru)', url: 'https://t.me/xsoulspace'), + ] + ), + ( + sectionTitle: 'Legal', + links: [ + // TODO(arenukvern): add page, + ( + title: 'Privacy', + url: + 'https://github.com/xsoulspace/last_answer/blob/master/PRIVACY_POLICY.md' + ), + // TODO(arenukvern): add page, + ( + title: 'Terms', + url: + 'https://github.com/xsoulspace/last_answer/blob/master/TERMS_AND_CONDITIONS.md' + ), + // TODO(arenukvern): add page, + ( + title: 'License', + url: 'https://github.com/xsoulspace/last_answer/blob/master/LICENSE' + ), + ] + ), +]; + +typedef FooterBottomBarLinkTuple = ({ + String sectionTitle, + List links, +}); + +class FotterBottomBar extends StatelessComponent { + const FotterBottomBar({super.key}); + @override + Iterable build(BuildContext context) sync* { + yield footer([ + div([ + section([ + nav([ + ..._bottomLinks.map( + (e) => div( + [ + h5( + [text(e.sectionTitle)], + classes: 'text-base font-semibold text-stone-800', + ), + ul( + e.links + .map( + (link) => LiLinkButton( + item: link, + decoration: LiLinkButtonDecoration.stone, + ), + ) + .toList(), + classes: 'mt-2 space-y-1', + ), + ], + ), + ), + ], classes: 'grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-6'), + TwSpacer.y('6'), + hr(), + // TODO(arenukvern): add social icons with links, + p( + [text('© 2020-2024 Anton Malofeev, Irina Veter.')], + classes: 'mt-8 text-xs leading-5 text-stone-500', + ), + ]) + ], classes: 'mx-auto max-w-7xl px-12 py-8'), + ], classes: 'z-10 border-t border-stone-100/80'); + } +} + +class AppBarBurgerButton extends StatelessComponent { + const AppBarBurgerButton({super.key}); + @override + Iterable build(BuildContext context) sync* { + yield div([ + button([text('Menu')], classes: '') + ], classes: 'ml-auto md:hidden'); + } +} + +class OpenAppButton extends StatelessComponent { + const OpenAppButton({ + this.decoration = StyledButtonDecoration.filled, + required this.title, + super.key, + }); + final StyledButtonDecoration decoration; + final String title; + @override + Iterable build(BuildContext context) sync* { + yield LinkButton( + styledDecoration: decoration, + title: p([ + text(title), + IconSpan(icon: MaterialIcons.chevronRight), + ], classes: 'flex items-center'), + url: 'https://xsoulspace.dev/last_answer', + ); + } +} diff --git a/packages/landing/lib/sections/download_screen.dart b/packages/landing/lib/sections/download_screen.dart new file mode 100644 index 00000000..d9f0aab9 --- /dev/null +++ b/packages/landing/lib/sections/download_screen.dart @@ -0,0 +1,135 @@ +import 'package:jaspr/ui.dart'; +import 'package:landing/app.dart'; +import 'package:landing/ui_kit/ui_kit.dart'; + +final _platforms = [ + ( + links: [ + ( + title: 'Google Play', + url: + 'https://play.google.com/store/apps/details?id=dev.xsoulspace.lastanswer' + ), + ], + platformTitle: 'Android' + ), + ( + links: [ + ( + title: 'Snapstore (Arm64, Amd64)', + url: 'https://snapcraft.io/last-answer' + ), + ], + platformTitle: 'Linux' + ), + ( + links: [ + ( + title: 'Microsoft Store (x86)', + url: 'https://apps.microsoft.com/detail/9n1r319w0rvd' + ), + ], + platformTitle: 'Windows' + ), +]; + +class DownloadScreen extends StatelessComponent { + const DownloadScreen({super.key}); + @override + Iterable build(BuildContext context) sync* { + yield section([ + Card([ + h2([text('Last Answer')], + classes: + 'text-5xl md:text-7xl tracking-tighter leading-2 md:leading-tight'), + h4([text('Notes & Ideas')], + classes: 'text-2xl md:text-3xl text-stone-800/70'), + TwSpacer.y('8'), + div([ + Row( + mainAxisAlignment: JustifyContent.spaceBetween, + children: [ + p([ + text( + 'Simple and quick app to write your thoughts. Also, it is open source.') + ], classes: 'w-fit'), + TwSpacer.x('8'), + div([], classes: 'w-max') + ], + ), + ], classes: 'w-full'), + TwSpacer.y('4 md:mt-8'), + Row(children: [ + LinkButton( + styledDecoration: StyledButtonDecoration.outlined, + openInNewTab: true, + url: 'https://github.com/xsoulspace/last_answer', + titleText: 'View on GitHub', + ), + TwSpacer.x('4'), + OpenAppButton( + title: 'Open app', + ), + ]), + TwSpacer.y('12 md:mt-12'), + div([hr()], classes: 'w-full'), + TwSpacer.y('12'), + h5([ + text('Platforms'), + ], classes: 'text-2xl font-light text-stone-800/60'), + TwSpacer.y('4'), + ..._platforms.map( + (e) => _PlatformTile(platform: e), + ), + ]), + ], + classes: + 'relative mx-auto max-w-4xl px-4 pb-8 pt-2 md:pb-16 text-center'); + } +} + +typedef PlatformLinksTuple = ({ + String platformTitle, + Iterable links, +}); + +typedef PlatformLinkTuple = ({ + String title, + String url, +}); + +class _PlatformTile extends StatelessComponent { + const _PlatformTile({required this.platform, super.key}); + final PlatformLinksTuple platform; + @override + Iterable build(BuildContext context) sync* { + yield div([ + Row( + mainAxisAlignment: JustifyContent.spaceBetween, + children: [ + strong([text(platform.platformTitle)], classes: ''), + div( + platform.links + .map( + (e) => LinkButton( + liDecoration: LiLinkButtonDecoration.emeraldLight, + url: e.url, + title: div([ + IconSpan( + icon: MaterialIcons.openInNew, + classes: 'text-sm', + ), + TwSpacer.x('2'), + text(e.title), + ], classes: 'flex items-center'), + openInNewTab: true, + ), + ) + .toList(), + classes: 'w-1/2 flex flex-col', + ), + ], + ), + ], classes: 'pb-4 pt-4 border-b last:border-b-0 border-stone-100 w-full'); + } +} diff --git a/packages/landing/lib/sections/home_screen.dart b/packages/landing/lib/sections/home_screen.dart new file mode 100644 index 00000000..457129ca --- /dev/null +++ b/packages/landing/lib/sections/home_screen.dart @@ -0,0 +1,165 @@ +import 'package:jaspr/jaspr.dart'; +import 'package:landing/app.dart'; +import 'package:landing/ui_kit/ui_kit.dart'; + +class HomeScreen extends StatelessComponent { + const HomeScreen({super.key}); + @override + Iterable build(BuildContext context) sync* { + yield div([ + section([ + div( + [ + h1( + [text('Think fast, write faster')], + classes: 'text-5xl md:text-8xl tracking-tighter' + 'font-bold leading-2 md:leading-tight text-stone-900/80', + ) + ], + classes: 'max-w-sm md:max-w-md mx-auto', + ), + TwSpacer.y('8 md:mt-20'), + p([ + text( + '🍃 Last Answer designed to never miss a moment of inspiration ✨') + ], + classes: + 'relative max-w-xs md:max-w-xl mx-auto text-xl md:text-3xl leading-relaxed md:leading-10 text-stone-600/80'), + TwSpacer.y('8 md:mt-16'), + StyledButton( + decoration: StyledButtonDecoration.filled.copyWith( + edgeInsets: 'pl-6 pr-4 py-3', + textStyle: 'text-md font-semibold', + ), + title: div([ + text('Start for free'), + IconSpan(icon: MaterialIcons.chevronRight) + ], classes: 'flex items-center'), + onClick: () => context.router.pushNamed(RoutePaths.download), + ), + TwSpacer.y('8'), + ], + classes: 'relative mx-auto max-w-5xl text-center ' + 'px-4 pb-4 pt-16 md:py-28 md:pt-28 md:pb-18') + ]); + + yield div([ + section([ + BentoGrid( + cards: [ + ...[ + ( + title: '- Wait, another note app? 👀', + description: + 'Yes 😉. You know, sometimes, it\'s not that hard to get inspiration, ' + 'but tooo much waiting for the right app will be open. ' + 'And in that moment - thoughts forgotten and time is lost. ' + 'So, with Last Answer we\'ve made it. ', + ), + ( + title: 'Start writing before data is loaded', + description: + 'Last Answer mission is to reduce any delays between ' + 'thought and writing - if you see the buttons ' + 'to create a Note or an Idea - you can instantly ⚡️ use ' + 'them without waiting.', + ), + ( + title: 'Offline-first 🏡, your ideas has priority', + description: + 'To give you fast access to your Notes and Ideas the app loads them as fast as possible, and only then, it loads theme, settings and other preferences. All your data stored in your device.', + ), + ( + title: 'Make Note', + description: 'To convert quickly thoughts to words.', + ), + ( + title: 'Write Post ', + description: + 'You can write draft of post in Last Answer by using character limits specific to social networks. For example Discord has limit 2000:)', + ), + ( + title: 'Brainstorm Idea', + description: + 'You can use various technics, such as 5 why, six sigmas or similar. Just try it', + ), + ( + title: 'Share it! ', + description: + 'Every note is simple plain text which can be easily copied to any app. Ideas a little bit more complex, but they are also support sharing as simple text.', + ), + ( + title: "Read Flow", + description: + "We write everyday in messangers, and they have certain flow - to read a chat your eye goes from bottom to up, the same way as you would write on typewriter machine - so as the LastAnswer. Every Note or Idea, designed to feel like a focused chat.", + ), + ( + title: 'Updates goes down -& up (if needed)', + description: + 'Last Answer has auto sorting function - you will see most recent notes are which you edited. Just like in messangers:)' + ), + ( + title: 'Configurable', + description: + 'You can change direction of Notes and Ideas in settings. As the Theme and Language.', + ), + ( + title: 'Backup & Restore', + description: + 'You can always save all data to one single file and restore from it.', + ) + ].map((e) => Card([ + TwSpacer.y('2'), + h3([text(e.title)], + classes: + 'font-semibold text-3xl tracking-wide md:text-4xl'), + TwSpacer.y('8'), + p([text(e.description)], classes: 'text-xl'), + TwSpacer.y('2'), + ], classes: '')) + ], + ), + ], classes: 'relative mx-auto max-w-4xl px-4 py-16 md:p-24 text-center'), + ], classes: 'bg-zinc-300/10 rounded-[28px] shadow'); + + yield section([ + TwSpacer.y('16 md:mt-20'), + div( + [ + h1( + // TODO(arenukvern): think about phrase, it's too much:) + [text('Never lose thoughts in the breeze of time')], + classes: 'text-4xl md:text-5xl tracking-tighter' + 'font-bold leading-2 md:leading-tight text-stone-900/80', + ) + ], + classes: 'max-w-sm md:max-w-md mx-auto', + ), + TwSpacer.y('16 md:mt-20'), + div( + [ + OpenAppButton( + decoration: StyledButtonDecoration.filled.copyWith( + edgeInsets: 'pl-6 pr-4 py-3', + textStyle: 'text-md font-semibold', + ), + title: 'Open app', + ), + TwSpacer.x('12 md:mr-24'), + LinkButton( + titleText: 'Download', + url: RoutePaths.download, + liDecoration: LiLinkButtonDecoration.emeraldNormal.copyWith( + textStyle: 'text-xl font-semibold', + ), + ), + ], + classes: 'flex flex-row items-center justify-center', + ), + // TODO(arenukvern): add community links, + TwSpacer.y('20 md:mt-24'), + ], + classes: 'relative mx-auto max-w-5xl text-center ' + 'px-4 pb-4 pt-16 md:py-28 md:pt-28 md:pb-18'); + } +} diff --git a/packages/landing/lib/sections/sections.dart b/packages/landing/lib/sections/sections.dart new file mode 100644 index 00000000..aba4b04c --- /dev/null +++ b/packages/landing/lib/sections/sections.dart @@ -0,0 +1,2 @@ +export 'download_screen.dart'; +export 'home_screen.dart'; diff --git a/packages/landing/lib/ui_kit/bars.dart b/packages/landing/lib/ui_kit/bars.dart new file mode 100644 index 00000000..3c69847f --- /dev/null +++ b/packages/landing/lib/ui_kit/bars.dart @@ -0,0 +1,41 @@ +import 'package:jaspr/jaspr.dart'; + +class TabBar extends StatelessComponent { + const TabBar({ + required this.tabs, + super.key, + }); + final List tabs; + @override + Iterable build(BuildContext context) sync* { + yield div( + [ul(tabs, classes: 'flex flex-row')], + classes: 'relative mx-auto max-w-lg w-full overflow-x-auto rounded-full ' + 'border-black/5 bg-black/5 p-1 backdrop-blur-2xl', + ); + } +} + +class Tab extends StatelessComponent { + const Tab({ + this.titleText = '', + this.title, + required this.isSelected, + required this.onClick, + super.key, + }); + final VoidCallback onClick; + final String titleText; + final Component? title; + final bool isSelected; + @override + Iterable build(BuildContext context) sync* { + yield li([ + button( + [title ?? text(titleText)], + classes: 'font-semibold text-stone-500/90', + onClick: onClick, + ), + ], classes: 'w-auto text-nowrap cursor-pointer select-none px-4 py-2'); + } +} diff --git a/packages/landing/lib/ui_kit/buttons.dart b/packages/landing/lib/ui_kit/buttons.dart new file mode 100644 index 00000000..c46333a8 --- /dev/null +++ b/packages/landing/lib/ui_kit/buttons.dart @@ -0,0 +1,168 @@ +import 'package:jaspr/ui.dart'; +import 'package:landing/app.dart'; + +class StyledButtonDecoration { + const StyledButtonDecoration( + this.classes, { + this.edgeInsets = 'px-3 py-2', + this.textStyle = 'text-sm font-semibold', + this.transitions = 'duration-100', + }); + static const filled = StyledButtonDecoration( + 'bg-emerald-500 hover:bg-emerald-600 ' + 'active:bg-emerald-700 text-white ' + 'rounded-full focus:outline-none', + ); + static const outlined = StyledButtonDecoration( + 'border border-emerald-500 hover:border-emerald-600 active:border-emerald-700 ' + 'text-emerald-500 hover:text-emerald-600 text-center ' + 'rounded-full bg-emerald-200/20 hover:bg-emerald-200/40 active:bg-emerald-700' + 'focus:outline-none', + textStyle: 'text-sm', + ); + static const text = StyledButtonDecoration(''); + final String classes; + final String edgeInsets; + final String textStyle; + final String transitions; + + @override + String toString() => + [edgeInsets, classes, textStyle, 'cursor-pointer'].join(' '); + + StyledButtonDecoration copyWith({String? edgeInsets, String? textStyle}) => + StyledButtonDecoration( + classes, + edgeInsets: edgeInsets ?? this.edgeInsets, + textStyle: textStyle ?? this.textStyle, + ); +} + +class StyledButton extends StatelessComponent { + const StyledButton({ + required this.decoration, + this.titleText = '', + this.onClick, + this.title, + super.key, + }); + final VoidCallback? onClick; + final StyledButtonDecoration decoration; + final String titleText; + final Component? title; + @override + Iterable build(BuildContext context) sync* { + yield button([title ?? text(titleText)], + classes: decoration.toString(), onClick: onClick); + } +} + +typedef LinkButtonTuple = ({String title, String url}); + +class LiLinkButtonDecoration { + const LiLinkButtonDecoration( + this.classes, { + this.textStyle = 'font-normal', + this.transitions = 'duration-100', + }); + static const stone900 = LiLinkButtonDecoration( + 'text-stone-900/80 hover:text-stone-900/90', + textStyle: 'text-xs', + ); + static const stone = LiLinkButtonDecoration( + 'text-stone-500/80 hover:text-stone-600/90', + textStyle: 'text-xs', + ); + static const emeraldLight = LiLinkButtonDecoration('hover:text-emerald-500'); + static final emeraldNormal = emeraldLight.copyWith(textStyle: 'font-normal'); + final String classes; + final String textStyle; + final String transitions; + + LiLinkButtonDecoration copyWith({String? textStyle}) => + LiLinkButtonDecoration( + classes, + textStyle: textStyle ?? this.textStyle, + ); + + @override + String toString() => [classes, textStyle, transitions].join(' '); +} + +class LiLinkButton extends StatelessComponent { + LiLinkButton({ + required this.item, + required this.decoration, + this.openInNewTab = false, + }); + final LiLinkButtonDecoration? decoration; + final LinkButtonTuple item; + final bool openInNewTab; + + @override + Iterable build(BuildContext context) sync* { + yield li([ + LinkButton( + titleText: item.title, + url: item.url, + liDecoration: decoration, + openInNewTab: openInNewTab, + ) + ]); + } +} + +class LinkButton extends StatelessComponent { + const LinkButton({ + this.liDecoration, + this.styledDecoration, + this.openInNewTab = false, + this.title, + this.titleText = '', + this.url = '', + super.key, + }) : classes = ''; + const LinkButton.raw({ + this.openInNewTab = false, + this.title, + this.titleText = '', + this.url = '', + this.classes = '', + super.key, + }) : liDecoration = null, + styledDecoration = null; + final Component? title; + final String titleText; + final String url; + final bool openInNewTab; + final LiLinkButtonDecoration? liDecoration; + final StyledButtonDecoration? styledDecoration; + final String classes; + @override + Iterable build(BuildContext context) sync* { + assert(url.isNotEmpty); + + final classes = + (liDecoration ?? styledDecoration)?.toString() ?? this.classes; + final title = this.title; + final shouldUseA = + url.startsWith('https://') || url.startsWith('http://') || openInNewTab; + + final children = [title ?? text(titleText)]; + // TODO(arenukvern): description, https://github.com/schultek/jaspr/issues/180 + if (shouldUseA) { + yield a( + children, + href: url, + target: shouldUseA ? Target.blank : null, + classes: classes, + ); + } else { + yield button( + children, + onClick: () => context.router.pushNamed(url), + classes: classes, + ); + } + } +} diff --git a/packages/landing/lib/ui_kit/containers.dart b/packages/landing/lib/ui_kit/containers.dart new file mode 100644 index 00000000..fd5941e0 --- /dev/null +++ b/packages/landing/lib/ui_kit/containers.dart @@ -0,0 +1,26 @@ +import 'package:jaspr/jaspr.dart'; + +class Card extends StatelessComponent { + const Card(this.children, {this.classes = '', super.key}); + final List children; + final String classes; + + @override + Iterable build(BuildContext context) sync* { + yield div(children, + classes: 'rounded-[64px] border ' + 'border-stone-100/80 bg-white/90 p-8 md:p-24 ' + '$classes flex flex-col items-start text-left'); + } +} + +class BentoGrid extends StatelessComponent { + const BentoGrid({required this.cards, super.key}); + final List cards; + @override + Iterable build(BuildContext context) sync* { + yield div(cards, + classes: + 'grid grid-flow-row-dense grid-cols-1 sm:grid-cols-1 gap-16 md:gap-24'); + } +} diff --git a/packages/landing/lib/ui_kit/material_icons.dart b/packages/landing/lib/ui_kit/material_icons.dart new file mode 100644 index 00000000..68e3f9ab --- /dev/null +++ b/packages/landing/lib/ui_kit/material_icons.dart @@ -0,0 +1,27 @@ +import 'package:jaspr/jaspr.dart'; + +/// https://fonts.google.com/icons?selected=Material+Symbols+Outlined:chevron_right:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=arrow +class MaterialIcons { + static const chevronRight = UiIcon('chevron_right'); + static const downloadForOffline = UiIcon('download_for_offline'); + static const download = UiIcon('download'); + static const openInNew = UiIcon('open_in_new'); +} + +class UiIcon { + const UiIcon(this.code); + final String code; + @override + String toString() => code; +} + +class IconSpan extends StatelessComponent { + const IconSpan({required this.icon, this.classes = '', super.key}); + final UiIcon icon; + final String classes; + @override + Iterable build(BuildContext context) sync* { + yield span([text('$icon')], + classes: 'material-symbols-outlined p-0 m-0 $classes'); + } +} diff --git a/packages/landing/lib/ui_kit/spacers.dart b/packages/landing/lib/ui_kit/spacers.dart new file mode 100644 index 00000000..0c80f4e4 --- /dev/null +++ b/packages/landing/lib/ui_kit/spacers.dart @@ -0,0 +1,12 @@ +import 'package:jaspr/jaspr.dart'; + +/// will use m-{value} to define spacing +class TwSpacer extends StatelessComponent { + const TwSpacer.x(final String value, {super.key}) : value = 'mr-$value'; + const TwSpacer.y(final String value, {super.key}) : value = 'mt-$value'; + final String value; + @override + Iterable build(BuildContext context) sync* { + yield div([], classes: value); + } +} diff --git a/packages/landing/lib/ui_kit/ui_kit.dart b/packages/landing/lib/ui_kit/ui_kit.dart new file mode 100644 index 00000000..618641a6 --- /dev/null +++ b/packages/landing/lib/ui_kit/ui_kit.dart @@ -0,0 +1,5 @@ +export 'bars.dart'; +export 'buttons.dart'; +export 'containers.dart'; +export 'material_icons.dart'; +export 'spacers.dart'; diff --git a/packages/landing/pubspec.lock b/packages/landing/pubspec.lock new file mode 100644 index 00000000..b06f40e6 --- /dev/null +++ b/packages/landing/pubspec.lock @@ -0,0 +1,605 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + archive: + dependency: transitive + description: + name: archive + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + url: "https://pub.dev" + source: hosted + version: "3.4.10" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bazel_worker: + dependency: transitive + description: + name: bazel_worker + sha256: "4eef19cc486c289e4b06c69d0f6f3192e85cc93c25d4d15d02afb205e388d2f0" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + binary_codec: + dependency: transitive + description: + name: binary_codec + sha256: "368144225d749e1e33f2f4628d0c70bffff99b99b1d6c0777b039f8967365b07" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_modules: + dependency: transitive + description: + name: build_modules + sha256: "66f0f746a239ff6cceba9d235a679ec70a6d9037ddddb36a24a0791a639a8486" + url: "https://pub.dev" + source: hosted + version: "5.0.7" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + url: "https://pub.dev" + source: hosted + version: "2.4.8" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "30859c90e9ddaccc484f56303931f477b1f1ba2bab74aa32ed5d6ce15870f8cf" + url: "https://pub.dev" + source: hosted + version: "7.2.8" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e + url: "https://pub.dev" + source: hosted + version: "8.9.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: "direct dev" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + jaspr: + dependency: "direct main" + description: + name: jaspr + sha256: "9508d5579e7a90ded91e11cf59b5ae051841800cbf06bac87551cc75e90ec560" + url: "https://pub.dev" + source: hosted + version: "0.10.0" + jaspr_builder: + dependency: "direct dev" + description: + name: jaspr_builder + sha256: "47ac451d85efca28ca60f96edbde63424469015ce4127b40b2bcda0b0683c5e0" + url: "https://pub.dev" + source: hosted + version: "0.10.0" + jaspr_router: + dependency: "direct main" + description: + name: jaspr_router + sha256: a8086776a5db0778d77054cba354bdb8bf73a748c0689bad6adc479f8c03d801 + url: "https://pub.dev" + source: hosted + version: "0.3.1" + jaspr_web_compilers: + dependency: "direct dev" + description: + name: jaspr_web_compilers + sha256: e40d0b79c5a75804aa22c257b798354c3c07c7661ae1f942045699348582aee6 + url: "https://pub.dev" + source: hosted + version: "4.0.7+1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + url: "https://pub.dev" + source: hosted + version: "3.7.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + scratch_space: + dependency: transitive + description: + name: scratch_space + sha256: "8510fbff458d733a58fc427057d1ac86303b376d609d6e1bc43f240aad9aa445" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_gzip: + dependency: transitive + description: + name: shelf_gzip + sha256: "4f4b793c0f969f348aece1ab4cc05fceba9fea431c1ce76b1bc0fa369cecfc15" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + shelf_proxy: + dependency: transitive + description: + name: shelf_proxy + sha256: a71d2307f4393211930c590c3d2c00630f6c5a7a77edc1ef6436dfd85a6a7ee3 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: e7d5ecd604e499358c5fe35ee828c0298a320d54455e791e9dcf73486bc8d9f0 + url: "https://pub.dev" + source: hosted + version: "14.1.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" + url: "https://pub.dev" + source: hosted + version: "2.4.4" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.3.0 <3.4.0" diff --git a/packages/landing/pubspec.yaml b/packages/landing/pubspec.yaml new file mode 100644 index 00000000..f03e12ce --- /dev/null +++ b/packages/landing/pubspec.yaml @@ -0,0 +1,20 @@ +name: landing +description: A pure client-side app without ssr. +version: 0.0.1 + +environment: + sdk: ">=3.3.0 <4.0.0" + +dependencies: + jaspr: ^0.10.0 + jaspr_router: ^0.3.1 + +dev_dependencies: + build_runner: ^2.4.0 + collection: ^1.18.0 + jaspr_builder: ^0.10.0 + jaspr_web_compilers: ^4.0.7+1 + lints: ^2.1.0 + +jaspr: + uses-ssr: false diff --git a/packages/landing/tailwind.config.js b/packages/landing/tailwind.config.js new file mode 100644 index 00000000..b6387a64 --- /dev/null +++ b/packages/landing/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./build/**/*.{js,ts,jsx,tsx,html}"], + theme: { + extend: {}, + }, + + plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")], +}; diff --git a/packages/landing/tailwindcss b/packages/landing/tailwindcss new file mode 100755 index 00000000..29df5e63 Binary files /dev/null and b/packages/landing/tailwindcss differ diff --git a/packages/landing/web/assets/images/idea_gemini_generated.jpg b/packages/landing/web/assets/images/idea_gemini_generated.jpg new file mode 100644 index 00000000..f8ecab7a Binary files /dev/null and b/packages/landing/web/assets/images/idea_gemini_generated.jpg differ diff --git a/packages/landing/web/index.html b/packages/landing/web/index.html new file mode 100644 index 00000000..0facdb04 --- /dev/null +++ b/packages/landing/web/index.html @@ -0,0 +1,29 @@ + + + + + + + + + LastAnswer: Notes & Ideas + + + + + + + + + + + + + + diff --git a/packages/landing/web/main.dart b/packages/landing/web/main.dart new file mode 100644 index 00000000..1a1d0309 --- /dev/null +++ b/packages/landing/web/main.dart @@ -0,0 +1,10 @@ +// Any .dart file inside the /web directory is compiled to javascript +// and executed in the browser. + +import 'package:jaspr/jaspr.dart'; +import 'package:landing/app.dart'; + +void main() { + // Attaches the [App] component to the of the page. + runApp(App()); +} diff --git a/packages/landing/web/output.css b/packages/landing/web/output.css new file mode 100644 index 00000000..e69de29b diff --git a/packages/landing/web/styles.css b/packages/landing/web/styles.css new file mode 100644 index 00000000..108887a8 --- /dev/null +++ b/packages/landing/web/styles.css @@ -0,0 +1,12 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +/* @import url(https://fonts.googleapis.com/css?family=Roboto); */ + +html, body { + /* font-family: 'Roboto', sans-serif; */ + /* width: 100%; + height: 100%; */ + margin: 0; + padding: 0; +} diff --git a/packages/server/config/passwords.sample.yaml b/packages/server/config/passwords.sample.yaml index 31bdd067..d770f5cb 100644 --- a/packages/server/config/passwords.sample.yaml +++ b/packages/server/config/passwords.sample.yaml @@ -12,7 +12,7 @@ # Save passwords used across all configurations here. shared: mySharedPassword: "my password" - + telegramBotToken: "" # These are passwords used when running the server locally in development mode development: database: "" diff --git a/packages/server/lib/envs.dart b/packages/server/lib/envs.dart index 49beeba9..8c59d739 100644 --- a/packages/server/lib/envs.dart +++ b/packages/server/lib/envs.dart @@ -1,3 +1,7 @@ +// ignore_for_file: do_not_use_environment + +import 'package:serverpod/serverpod.dart'; + class Envs { Envs._(); static const androidPackageId = 'dev.xsoulspace.lastanswer'; @@ -7,4 +11,7 @@ class Envs { static const bundleId = 'your iOS bundle ID'; static const googlePlayProjectName = 'Google Cloud project name'; static const googlePlayPubsubBillingTopic = 'play_billing'; + static const telegramWebAppUrl = 'https://xsoulspace.dev/last_answer'; + static final telegramBotToken = + Serverpod.instance.getPassword('telegramBotToken') ?? ''; } diff --git a/packages/server/lib/server.dart b/packages/server/lib/server.dart index ccef7e9c..11e2e337 100644 --- a/packages/server/lib/server.dart +++ b/packages/server/lib/server.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:server/src/endpoints/modules/modules.dart'; import 'package:server/src/generated/endpoints.dart'; import 'package:server/src/generated/protocol.dart'; diff --git a/packages/server/pubspec.lock b/packages/server/pubspec.lock index 30368f64..68abf81a 100644 --- a/packages/server/pubspec.lock +++ b/packages/server/pubspec.lock @@ -535,10 +535,10 @@ packages: dependency: transitive description: name: postgres - sha256: fce8406bbe8b7018c768e76816be24adf302f44c06d7176c912d2501ea6aac2a + sha256: f8e4f14734d096277f77ed5dddefcbc1ce18f8f7db5b7ff4b5dd6df2d9db2730 url: "https://pub.dev" source: hosted - version: "2.6.3" + version: "2.6.4" postgres_pool: dependency: transitive description: @@ -599,50 +599,50 @@ packages: dependency: "direct main" description: name: serverpod - sha256: "9be18d3a7cc13edba1a6e3756172a786f3b75ec7ace02c126327f29e8afafa20" + sha256: "6f4437ce09217fea78a3aa8b6c2148ecb4711e9b38a27a3d48f014af0a7b0026" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_auth_server: dependency: "direct main" description: name: serverpod_auth_server - sha256: "0a7bd16f3ce7b6aee4ea795f75818a6dd8b47dc96f0db9541e1d5f34564164dc" + sha256: "888bb992251c590b166fa0433a9ef8c4c546cd47ec216af64b67a340b0f29b07" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_client: dependency: transitive description: name: serverpod_client - sha256: a796409e777c0ebba3dafc62b68f6f4597e454ce1b96f3d66255176f9fe34150 + sha256: fab1200137b18bc1f8a7cae79491245fe9391a931a0db13a56cab1a9116b56a1 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_serialization: dependency: transitive description: name: serverpod_serialization - sha256: e1bc1ac697ac5afe6c60ffbdefb5ea473f4de315ee2f29fa233cb5c6556ef966 + sha256: b6a607a1431187b1d4e6f0f1f091d88e861c7c4ec28dc46e121dbe2757339f6e url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_service_client: dependency: transitive description: name: serverpod_service_client - sha256: ca9639642fa2ce519915fc65d4bc7bf18d63fe2c49d80a3e8fa589810a1d4940 + sha256: "45c49c6ef1157b001752c6945dfd90ffae21dc4c08535426de51bc4e5ed26d26" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_shared: dependency: transitive description: name: serverpod_shared - sha256: "3eee3d7750412ba5fb971e1e577a999e852fda28658a535b71643ce31a6d7482" + sha256: "878ebfd26ab9070406a7fe4324b68b676a84bccd3f79dcb536c81429ba6c3c85" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" shared_models: dependency: "direct main" description: diff --git a/packages/server/pubspec.yaml b/packages/server/pubspec.yaml index c7f0bfae..0eba7d01 100644 --- a/packages/server/pubspec.yaml +++ b/packages/server/pubspec.yaml @@ -9,8 +9,8 @@ dependencies: app_store_server_sdk: ^1.2.5 googleapis: ^11.0.0 googleapis_auth: ^1.4.1 - serverpod: 1.2.3 - serverpod_auth_server: 1.2.3 + serverpod: 1.2.5 + serverpod_auth_server: 1.2.5 shared_models: path: ../../packages/shared_models diff --git a/packages/shared_models/lib/src/foundation/network/models/models.g.dart b/packages/shared_models/lib/src/foundation/network/models/models.g.dart index bbc60bdc..099da3d1 100644 --- a/packages/shared_models/lib/src/foundation/network/models/models.g.dart +++ b/packages/shared_models/lib/src/foundation/network/models/models.g.dart @@ -12,7 +12,7 @@ PaginatedPageRequestModel _$PaginatedPageRequestModelFromJson( ) => PaginatedPageRequestModel( page: json['page'] as int? ?? 0, - limit: json['limit'] as int? ?? 5, + limit: json['limit'] as int? ?? 10, data: _$nullableGenericFromJson(json['data'], fromJsonTData), ); diff --git a/packages/shared_models/lib/src/models/models.freezed.dart b/packages/shared_models/lib/src/models/models.freezed.dart index 52d0b0f7..2f53d4cc 100644 --- a/packages/shared_models/lib/src/models/models.freezed.dart +++ b/packages/shared_models/lib/src/models/models.freezed.dart @@ -1139,6 +1139,147 @@ abstract class _PurchasesModel extends PurchasesModel { throw _privateConstructorUsedError; } +AdsStateModel _$AdsStateModelFromJson(Map json) { + return _AdsStateModel.fromJson(json); +} + +/// @nodoc +mixin _$AdsStateModel { + DateTime? get lastDateWhenAdRewardReceived => + throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AdsStateModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AdsStateModelCopyWith<$Res> { + factory $AdsStateModelCopyWith( + AdsStateModel value, $Res Function(AdsStateModel) then) = + _$AdsStateModelCopyWithImpl<$Res, AdsStateModel>; + @useResult + $Res call({DateTime? lastDateWhenAdRewardReceived}); +} + +/// @nodoc +class _$AdsStateModelCopyWithImpl<$Res, $Val extends AdsStateModel> + implements $AdsStateModelCopyWith<$Res> { + _$AdsStateModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lastDateWhenAdRewardReceived = freezed, + }) { + return _then(_value.copyWith( + lastDateWhenAdRewardReceived: freezed == lastDateWhenAdRewardReceived + ? _value.lastDateWhenAdRewardReceived + : lastDateWhenAdRewardReceived // ignore: cast_nullable_to_non_nullable + as DateTime?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AdsStateModelImplCopyWith<$Res> + implements $AdsStateModelCopyWith<$Res> { + factory _$$AdsStateModelImplCopyWith( + _$AdsStateModelImpl value, $Res Function(_$AdsStateModelImpl) then) = + __$$AdsStateModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({DateTime? lastDateWhenAdRewardReceived}); +} + +/// @nodoc +class __$$AdsStateModelImplCopyWithImpl<$Res> + extends _$AdsStateModelCopyWithImpl<$Res, _$AdsStateModelImpl> + implements _$$AdsStateModelImplCopyWith<$Res> { + __$$AdsStateModelImplCopyWithImpl( + _$AdsStateModelImpl _value, $Res Function(_$AdsStateModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lastDateWhenAdRewardReceived = freezed, + }) { + return _then(_$AdsStateModelImpl( + lastDateWhenAdRewardReceived: freezed == lastDateWhenAdRewardReceived + ? _value.lastDateWhenAdRewardReceived + : lastDateWhenAdRewardReceived // ignore: cast_nullable_to_non_nullable + as DateTime?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AdsStateModelImpl implements _AdsStateModel { + const _$AdsStateModelImpl({this.lastDateWhenAdRewardReceived}); + + factory _$AdsStateModelImpl.fromJson(Map json) => + _$$AdsStateModelImplFromJson(json); + + @override + final DateTime? lastDateWhenAdRewardReceived; + + @override + String toString() { + return 'AdsStateModel(lastDateWhenAdRewardReceived: $lastDateWhenAdRewardReceived)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AdsStateModelImpl && + (identical(other.lastDateWhenAdRewardReceived, + lastDateWhenAdRewardReceived) || + other.lastDateWhenAdRewardReceived == + lastDateWhenAdRewardReceived)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, lastDateWhenAdRewardReceived); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AdsStateModelImplCopyWith<_$AdsStateModelImpl> get copyWith => + __$$AdsStateModelImplCopyWithImpl<_$AdsStateModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$AdsStateModelImplToJson( + this, + ); + } +} + +abstract class _AdsStateModel implements AdsStateModel { + const factory _AdsStateModel({final DateTime? lastDateWhenAdRewardReceived}) = + _$AdsStateModelImpl; + + factory _AdsStateModel.fromJson(Map json) = + _$AdsStateModelImpl.fromJson; + + @override + DateTime? get lastDateWhenAdRewardReceived; + @override + @JsonKey(ignore: true) + _$$AdsStateModelImplCopyWith<_$AdsStateModelImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$UserModelId { String get value => throw _privateConstructorUsedError; diff --git a/packages/shared_models/lib/src/models/models.g.dart b/packages/shared_models/lib/src/models/models.g.dart index 37cd0399..64cf9945 100644 --- a/packages/shared_models/lib/src/models/models.g.dart +++ b/packages/shared_models/lib/src/models/models.g.dart @@ -108,3 +108,16 @@ Map _$$PurchasesModelImplToJson( 'supporter_days_count': instance.supporterDaysCount, 'used_days_count': instance.usedDaysCount, }; + +_$AdsStateModelImpl _$$AdsStateModelImplFromJson(Map json) => + _$AdsStateModelImpl( + lastDateWhenAdRewardReceived: json['lastDateWhenAdRewardReceived'] == null + ? null + : DateTime.parse(json['lastDateWhenAdRewardReceived'] as String), + ); + +Map _$$AdsStateModelImplToJson(_$AdsStateModelImpl instance) => + { + 'lastDateWhenAdRewardReceived': + instance.lastDateWhenAdRewardReceived?.toIso8601String(), + }; diff --git a/packages/shared_models/lib/src/models/remote_user.dart b/packages/shared_models/lib/src/models/remote_user.dart index 567da166..3bfe90cb 100644 --- a/packages/shared_models/lib/src/models/remote_user.dart +++ b/packages/shared_models/lib/src/models/remote_user.dart @@ -78,3 +78,12 @@ class PurchasesModel with _$PurchasesModel { ); } } + +@freezed +class AdsStateModel with _$AdsStateModel { + const factory AdsStateModel({ + final DateTime? lastDateWhenAdRewardReceived, + }) = _AdsStateModel; + factory AdsStateModel.fromJson(final Map json) => + _$AdsStateModelFromJson(json); +} diff --git a/packages/shared_models/pubspec.lock b/packages/shared_models/pubspec.lock index 5bed5114..0e3c31c3 100644 --- a/packages/shared_models/pubspec.lock +++ b/packages/shared_models/pubspec.lock @@ -612,18 +612,18 @@ packages: dependency: "direct main" description: name: serverpod_client - sha256: a796409e777c0ebba3dafc62b68f6f4597e454ce1b96f3d66255176f9fe34150 + sha256: fab1200137b18bc1f8a7cae79491245fe9391a931a0db13a56cab1a9116b56a1 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_serialization: dependency: transitive description: name: serverpod_serialization - sha256: e1bc1ac697ac5afe6c60ffbdefb5ea473f4de315ee2f29fa233cb5c6556ef966 + sha256: b6a607a1431187b1d4e6f0f1f091d88e861c7c4ec28dc46e121dbe2757339f6e url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" shelf: dependency: transitive description: diff --git a/packages/shared_models/pubspec.yaml b/packages/shared_models/pubspec.yaml index 9ed51ce5..e7fba3e2 100644 --- a/packages/shared_models/pubspec.yaml +++ b/packages/shared_models/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: path_provider: ^2.1.1 path_to_regexp: ^0.4.0 quiver: ^3.2.1 - serverpod_client: 1.2.3 + serverpod_client: 1.2.5 universal_io: ^2.2.0 uuid: ^4.1.0 diff --git a/packages/tg_last_answer_bot/.gitignore b/packages/tg_last_answer_bot/.gitignore new file mode 100644 index 00000000..67dfe642 --- /dev/null +++ b/packages/tg_last_answer_bot/.gitignore @@ -0,0 +1,16 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by the Operating System +.DS_Store + +# Files and directories created by pub +.dart_tool/ +.packages +pubspec.lock + +# Files and directories created by dart_frog +build/ +.dart_frog + +# Test related files +coverage/ \ No newline at end of file diff --git a/packages/tg_last_answer_bot/.vscode/extensions.json b/packages/tg_last_answer_bot/.vscode/extensions.json new file mode 100644 index 00000000..be2e60ea --- /dev/null +++ b/packages/tg_last_answer_bot/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["VeryGoodVentures.dart-frog"] +} \ No newline at end of file diff --git a/packages/tg_last_answer_bot/Makefile b/packages/tg_last_answer_bot/Makefile new file mode 100644 index 00000000..b4fca2c6 --- /dev/null +++ b/packages/tg_last_answer_bot/Makefile @@ -0,0 +1,6 @@ +update-cli: + dart pub global activate dart_frog_cli +start: + TELEGRAM_BOT_TOKEN="" dart_frog dev +build: + TELEGRAM_BOT_TOKEN="" dart_frog build \ No newline at end of file diff --git a/packages/tg_last_answer_bot/README.md b/packages/tg_last_answer_bot/README.md new file mode 100644 index 00000000..b0bc24da --- /dev/null +++ b/packages/tg_last_answer_bot/README.md @@ -0,0 +1,12 @@ +# tg_last_answer_bot + +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![License: MIT][license_badge]][license_link] +[![Powered by Dart Frog](https://img.shields.io/endpoint?url=https://tinyurl.com/dartfrog-badge)](https://dartfrog.vgv.dev) + +An example application built with dart_frog + +[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg +[license_link]: https://opensource.org/licenses/MIT +[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg +[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis \ No newline at end of file diff --git a/packages/tg_last_answer_bot/analysis_options.yaml b/packages/tg_last_answer_bot/analysis_options.yaml new file mode 100644 index 00000000..40f65cf6 --- /dev/null +++ b/packages/tg_last_answer_bot/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:very_good_analysis/analysis_options.5.1.0.yaml +analyzer: + exclude: + - build/** +linter: + rules: + file_names: false diff --git a/packages/tg_last_answer_bot/envs.dart b/packages/tg_last_answer_bot/envs.dart new file mode 100644 index 00000000..9d3e6f7f --- /dev/null +++ b/packages/tg_last_answer_bot/envs.dart @@ -0,0 +1,7 @@ +import 'dart:io'; + +class Envs { + static const String telegramWebAppUrl = 'https://xsoulspace.dev/last_answer'; + static final telegramBotToken = + Platform.environment['TELEGRAM_BOT_TOKEN'] ?? ''; +} diff --git a/packages/tg_last_answer_bot/main.dart b/packages/tg_last_answer_bot/main.dart new file mode 100644 index 00000000..a1848328 --- /dev/null +++ b/packages/tg_last_answer_bot/main.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +import 'services/services.dart'; + +Future run(Handler handler, InternetAddress ip, int port) async { + // 1. Execute any custom code prior to starting the server... + final botService = TelegramBotService(); + + await botService.onLoad(); + + // 2. Use the provided `handler`, `ip`, and `port` to create a custom `HttpServer`. + // Or use the Dart Frog serve method to do that for you. + return serve(handler, ip, port)..whenComplete(botService.dispose); +} diff --git a/packages/tg_last_answer_bot/pubspec.yaml b/packages/tg_last_answer_bot/pubspec.yaml new file mode 100644 index 00000000..bda875f7 --- /dev/null +++ b/packages/tg_last_answer_bot/pubspec.yaml @@ -0,0 +1,16 @@ +name: tg_last_answer_bot +description: An new Dart Frog application +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + dart_frog: ^1.0.0 + televerse: ^1.13.2 + +dev_dependencies: + mocktail: ^1.0.0 + test: ^1.19.2 + very_good_analysis: ^5.1.0 diff --git a/packages/tg_last_answer_bot/routes/index.dart b/packages/tg_last_answer_bot/routes/index.dart new file mode 100644 index 00000000..a538147d --- /dev/null +++ b/packages/tg_last_answer_bot/routes/index.dart @@ -0,0 +1,5 @@ +import 'package:dart_frog/dart_frog.dart'; + +Response onRequest(RequestContext context) { + return Response(body: 'Welcome to Dart Frog!'); +} diff --git a/packages/tg_last_answer_bot/services/services.dart b/packages/tg_last_answer_bot/services/services.dart new file mode 100644 index 00000000..e89db2bf --- /dev/null +++ b/packages/tg_last_answer_bot/services/services.dart @@ -0,0 +1 @@ +export 'telegram_bot_service.dart'; diff --git a/packages/tg_last_answer_bot/services/telegram_bot_service.dart b/packages/tg_last_answer_bot/services/telegram_bot_service.dart new file mode 100644 index 00000000..84fb5703 --- /dev/null +++ b/packages/tg_last_answer_bot/services/telegram_bot_service.dart @@ -0,0 +1,52 @@ +import 'package:televerse/televerse.dart'; + +import '../envs.dart'; + +class TelegramBotService { + Bot? _bot; + bool get _isInitializable => Envs.telegramBotToken.isNotEmpty; + Future onLoad() async { + if (!_isInitializable) return; + final bot = _bot = Bot(Envs.telegramBotToken); + + bot.start((ctx) async => await ctx.reply('Hello!')); + + /// Sets up the /settings command listener + bot.settings((ctx) async => await ctx.reply('Settings')); + + /// Sets up the /help command listener + bot.help((ctx) async => await ctx.reply('Help')); + + /// The [bot.hears] method allows you to listen to messages that match a regular expression. + /// You can use the `Context.matches` getter to access the matches of the regular expression. + bot.hears(RegExp('Hello, (.*)!'), (ctx) async { + await ctx.reply('${ctx.matches![1]} must be a doing great!'); + }); + print('Starting telegram bot...'); + // await bot.start( + // (ctx) { + // // `TODO`(arenukvern): add language dependency, + // print('tada'); + // ctx.reply( + // // ignore: lines_longer_than_80_chars + // '👋, ${ctx.from?.username ?? ''}. Нажми кнопку ниже, чтобы открыть приложение.', + // replyMarkup: ReplyKeyboardMarkup( + // keyboard: [ + // [ + // const KeyboardButton( + // text: 'Открыть', + // webApp: WebAppInfo(url: Envs.telegramWebAppUrl), + // ), + // ] + // ], + // ), + // ); + // }, + // ); + } + + void dispose() { + print('Stopping telegram bot...'); + _bot?.stop(); + } +} diff --git a/packages/tg_last_answer_bot/test/routes/index_test.dart b/packages/tg_last_answer_bot/test/routes/index_test.dart new file mode 100644 index 00000000..6936e385 --- /dev/null +++ b/packages/tg_last_answer_bot/test/routes/index_test.dart @@ -0,0 +1,23 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../../routes/index.dart' as route; + +class _MockRequestContext extends Mock implements RequestContext {} + +void main() { + group('GET /', () { + test('responds with a 200 and "Welcome to Dart Frog!".', () { + final context = _MockRequestContext(); + final response = route.onRequest(context); + expect(response.statusCode, equals(HttpStatus.ok)); + expect( + response.body(), + completion(equals('Welcome to Dart Frog!')), + ); + }); + }); +} diff --git a/pubspec.lock b/pubspec.lock index cd6e6bd4..7cb40695 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1607,50 +1607,50 @@ packages: dependency: transitive description: name: serverpod_auth_client - sha256: "5d413353ded9b5875c97af0925efb5bdecd2c5f59d62c51cb569906fb0e16400" + sha256: a169b0ed8492404e29ef2ac9dfdd481726440e770037950be484de0d44b18368 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_auth_google_flutter: dependency: transitive description: name: serverpod_auth_google_flutter - sha256: "3205c46947afb6583b44c899c6857e2eabc6a0690f61d54947f1e7665e41cb8b" + sha256: aa254359a0f3131918dc7323cc2f88c26ac9385607c661375e3cfef3c944e2af url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_auth_shared_flutter: dependency: transitive description: name: serverpod_auth_shared_flutter - sha256: "0121480c8b91955015ceca0c5c6871788b47f9807119124a3b5cf0e3ec4d5dc2" + sha256: beef46ad7b3b7704a84b30101af97708560e38a08fef8f00194b53e9245cc782 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_client: dependency: transitive description: name: serverpod_client - sha256: a796409e777c0ebba3dafc62b68f6f4597e454ce1b96f3d66255176f9fe34150 + sha256: fab1200137b18bc1f8a7cae79491245fe9391a931a0db13a56cab1a9116b56a1 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_flutter: dependency: transitive description: name: serverpod_flutter - sha256: f9af6e3302abc142c7120dbd97d094abb2bcb1f34501aa617dfd6b0cdb2a8a9b + sha256: "5e117b028c669f159966c436483bd12670668d86e2bf0a5c8f4b4b0a84b1963d" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" serverpod_serialization: dependency: transitive description: name: serverpod_serialization - sha256: e1bc1ac697ac5afe6c60ffbdefb5ea473f4de315ee2f29fa233cb5c6556ef966 + sha256: b6a607a1431187b1d4e6f0f1f091d88e861c7c4ec28dc46e121dbe2757339f6e url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" share_plus: dependency: "direct main" description: @@ -1847,6 +1847,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + televerse: + dependency: transitive + description: + name: televerse + sha256: "1cf35170b12f1ada9d376ee03941e30c98769e1e79f6b259ffaa227e2573cecd" + url: "https://pub.dev" + source: hosted + version: "1.13.2" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 076927df..a2fda85a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 3.19.5+47 +version: 3.20.0+48 environment: sdk: ">=3.3.0 <4.0.0" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 4a1505e9..3466da08 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: last-answer -version: 3.19.5+47 -summary: Fast ideas brainstorming tool with quick notes +version: 3.20.0+48 +summary: App to quickly write Notes & Ideas description: | Do you have a cool idea and need to get it down quickly? Or do you blog / write articles on social media? diff --git a/web/index.html b/web/index.html index 23400138..6c24e3a6 100644 --- a/web/index.html +++ b/web/index.html @@ -19,12 +19,14 @@ - + + +