feat: allow users on staging to switch their environment (#2799)
This commit is contained in:
parent
6c3de08019
commit
33425f4406
24 changed files with 451 additions and 99 deletions
1
.env
1
.env
|
|
@ -1,3 +1,4 @@
|
|||
ENVIRONMENT = 'staging'
|
||||
CHOREO_API = 'https://api.staging.pangea.chat'
|
||||
FRONTEND_URL='https://app.staging.pangea.chat'
|
||||
SYNAPSE_URL = 'matrix.staging.pangea.chat'
|
||||
|
|
|
|||
2
.github/workflows/main_deploy.yaml
vendored
2
.github/workflows/main_deploy.yaml
vendored
|
|
@ -47,6 +47,8 @@ jobs:
|
|||
touch public/.env
|
||||
echo "$WEB_APP_ENV" >> public/.env
|
||||
cp public/.env public/assets/.env
|
||||
touch public/assets/envs.json
|
||||
echo "$ENV_OVERRIDES" >> public/assets/envs.json
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/manual.yml
vendored
2
.github/workflows/manual.yml
vendored
|
|
@ -45,6 +45,8 @@ jobs:
|
|||
rm assets/.env
|
||||
echo "$WEB_APP_ENV" >> .env
|
||||
cp .env assets/.env
|
||||
touch assets/envs.json
|
||||
echo "$ENV_OVERRIDES" >> assets/envs.json
|
||||
- name: Apply .env patch
|
||||
run: git apply ./scripts/enable_mobile_env.patch
|
||||
- name: Install Fastlane
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -18,6 +18,7 @@ keys.json
|
|||
!/public/.env
|
||||
*.env.local_choreo
|
||||
*.env.prod
|
||||
envs.json
|
||||
# libolm package
|
||||
/assets/js/package
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
ENVIRONMENT = 'staging'
|
||||
CHOREO_API = 'https://api.staging.pangea.chat'
|
||||
FRONTEND_URL = 'https://app.staging.pangea.chat'
|
||||
|
||||
|
|
|
|||
|
|
@ -4934,5 +4934,7 @@
|
|||
"joinSpaceOnboardingDesc": "Do you have an invite code or link to a learning community?",
|
||||
"skipForNow": "Skip for now",
|
||||
"permissions": "Permissions",
|
||||
"spaceChildPermission": "Who can add new chats and subspaces to this space"
|
||||
"spaceChildPermission": "Who can add new chats and subspaces to this space",
|
||||
"addEnvironmentOverride": "Add environment override",
|
||||
"defaultOption": "Default"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ abstract class AppConfig {
|
|||
static String? get applicationWelcomeMessage => _applicationWelcomeMessage;
|
||||
// #Pangea
|
||||
// static String _defaultHomeserver = 'matrix.org';
|
||||
static String _defaultHomeserver = Environment.synapseURL;
|
||||
static String get _defaultHomeserver => Environment.synapseURL;
|
||||
// #Pangea
|
||||
static String get defaultHomeserver => _defaultHomeserver;
|
||||
static double fontSizeFactor = 1;
|
||||
|
|
@ -206,9 +206,11 @@ abstract class AppConfig {
|
|||
if (json['application_welcome_message'] is String) {
|
||||
_applicationWelcomeMessage = json['application_welcome_message'];
|
||||
}
|
||||
if (json['default_homeserver'] is String) {
|
||||
_defaultHomeserver = json['default_homeserver'];
|
||||
}
|
||||
// #Pangea
|
||||
// if (json['default_homeserver'] is String) {
|
||||
// _defaultHomeserver = json['default_homeserver'];
|
||||
// }
|
||||
// Pangea#
|
||||
if (json['privacy_url'] is String) {
|
||||
_privacyUrl = json['privacy_url'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ Future<void> startGui(List<Client> clients, SharedPreferences store) async {
|
|||
// staging or vice versa, logout.
|
||||
if (firstClient?.userID?.domain != null) {
|
||||
final isStagingUser = firstClient!.userID!.domain!.contains("staging");
|
||||
final isStagingServer = Environment.isStaging;
|
||||
final isStagingServer = Environment.synapseURL.contains("staging");
|
||||
if (isStagingServer != isStagingUser) {
|
||||
await firstClient.logout();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ class SettingsView extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
// Conditional ListTile based on the environment (staging or not)
|
||||
if (Environment.isStaging)
|
||||
if (Environment.isStagingEnvironment)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.bug_report_outlined),
|
||||
title: Text(L10n.of(context).connectedToStaging),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.da
|
|||
|
||||
/// A minimized version of AnalyticsController that get the logged in user's analytics
|
||||
class GetAnalyticsController extends BaseController {
|
||||
final GetStorage analyticsBox = GetStorage("analytics_storage");
|
||||
static final GetStorage analyticsBox = GetStorage("analytics_storage");
|
||||
late PangeaController _pangeaController;
|
||||
late PracticeSelectionRepo perMessage;
|
||||
|
||||
|
|
@ -276,6 +276,15 @@ class GetAnalyticsController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> clearMessagesCache() async =>
|
||||
analyticsBox.remove(PLocalKey.messagesSinceUpdate);
|
||||
|
||||
Future<void> setMessagesCache(Map<dynamic, dynamic> cacheValue) async =>
|
||||
analyticsBox.write(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
cacheValue,
|
||||
);
|
||||
|
||||
/// A flat list of all locally cached construct uses
|
||||
List<OneConstructUse> get _locallyCachedConstructs =>
|
||||
messagesSinceUpdate.values.expand((e) => e).toList();
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ class LevelUpBannerState extends State<LevelUpBanner>
|
|||
}
|
||||
|
||||
Future<void> _toggleDetails() async {
|
||||
if (!Environment.isStaging) return;
|
||||
if (!Environment.isStagingEnvironment) return;
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
|
@ -282,7 +282,7 @@ class LevelUpBannerState extends State<LevelUpBanner>
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
if (Environment.isStaging)
|
||||
if (Environment.isStagingEnvironment)
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: _error == null
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart
|
|||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/base_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
|
|
@ -319,16 +318,14 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
/// Clears the local cache of recently sent constructs. Called before updating analytics
|
||||
void clearMessagesSinceUpdate({clearDrafts = false}) {
|
||||
if (clearDrafts) {
|
||||
MatrixState.pangeaController.getAnalytics.analyticsBox
|
||||
.remove(PLocalKey.messagesSinceUpdate);
|
||||
MatrixState.pangeaController.getAnalytics.clearMessagesCache();
|
||||
return;
|
||||
}
|
||||
|
||||
final localCache = _pangeaController.getAnalytics.messagesSinceUpdate;
|
||||
final draftKeys = localCache.keys.where((key) => key.startsWith('draft'));
|
||||
if (draftKeys.isEmpty) {
|
||||
MatrixState.pangeaController.getAnalytics.analyticsBox
|
||||
.remove(PLocalKey.messagesSinceUpdate);
|
||||
MatrixState.pangeaController.getAnalytics.clearMessagesCache();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -348,10 +345,8 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
final constructJsons = entry.value.map((e) => e.toJson()).toList();
|
||||
formattedCache[entry.key] = constructJsons;
|
||||
}
|
||||
await MatrixState.pangeaController.getAnalytics.analyticsBox.write(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
formattedCache,
|
||||
);
|
||||
await MatrixState.pangeaController.getAnalytics
|
||||
.setMessagesCache(formattedCache);
|
||||
}
|
||||
|
||||
/// Prevent concurrent updates to analytics
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:fluffychat/pangea/common/config/environment.dart';
|
|||
class BotName {
|
||||
static String get byEnvironment => Environment.botName != null
|
||||
? Environment.botName!
|
||||
: Environment.isStaging
|
||||
: Environment.isStagingEnvironment
|
||||
? "@bot:staging.pangea.chat"
|
||||
: "@bot:pangea.chat";
|
||||
static String get localBot => "@matrix-bot-test:staging.pangea.chat";
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ Map<String, dynamic> defaultPowerLevels(String userID) => {
|
|||
"m.room.tombstone": 100,
|
||||
},
|
||||
"users": {
|
||||
"@bot:staging.pangea.chat": 50,
|
||||
userID: 100,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,25 +1,35 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
|
||||
class Environment {
|
||||
static bool get itIsTime =>
|
||||
DateTime.utc(2023, 1, 25).isBefore(DateTime.now());
|
||||
|
||||
static String get fileName {
|
||||
return ".env";
|
||||
}
|
||||
|
||||
static bool get isStaging => synapseURL.contains("staging");
|
||||
static bool get isStagingEnvironment =>
|
||||
dotenv.env["ENVIRONMENT"] == "staging";
|
||||
|
||||
static String get frontendURL {
|
||||
return dotenv.env["FRONTEND_URL"] ?? "Frontend URL NOT FOUND";
|
||||
return appConfigOverride?.frontendURL ??
|
||||
dotenv.env["FRONTEND_URL"] ??
|
||||
"Frontend URL NOT FOUND";
|
||||
}
|
||||
|
||||
static String get synapseURL {
|
||||
return dotenv.env['SYNAPSE_URL'] ?? 'Synapse Url not found';
|
||||
return appConfigOverride?.synapseURL ??
|
||||
dotenv.env['SYNAPSE_URL'] ??
|
||||
'Synapse Url not found';
|
||||
}
|
||||
|
||||
static String get homeServer {
|
||||
String? homeServerFromSynapseURL = dotenv.env['SYNAPSE_URL'];
|
||||
String? homeServerFromSynapseURL =
|
||||
appConfigOverride?.synapseURL ?? dotenv.env['SYNAPSE_URL'];
|
||||
if (homeServerFromSynapseURL != null) {
|
||||
if (homeServerFromSynapseURL.startsWith("http://")) {
|
||||
homeServerFromSynapseURL =
|
||||
|
|
@ -34,13 +44,14 @@ class Environment {
|
|||
homeServerFromSynapseURL.replaceFirst("matrix.", "");
|
||||
}
|
||||
}
|
||||
return dotenv.env["HOME_SERVER"] ??
|
||||
return appConfigOverride?.homeServer ??
|
||||
dotenv.env["HOME_SERVER"] ??
|
||||
homeServerFromSynapseURL ??
|
||||
'Home Server not found';
|
||||
}
|
||||
|
||||
static String get choreoApi {
|
||||
final envEntry = dotenv.env['CHOREO_API'];
|
||||
final envEntry = appConfigOverride?.choreoApi ?? dotenv.env['CHOREO_API'];
|
||||
if (envEntry == null) {
|
||||
return "Not found";
|
||||
}
|
||||
|
|
@ -54,48 +65,214 @@ class Environment {
|
|||
}
|
||||
|
||||
static String get choreoApiKey {
|
||||
return dotenv.env['CHOREO_API_KEY'] ??
|
||||
return appConfigOverride?.choreoApiKey ??
|
||||
dotenv.env['CHOREO_API_KEY'] ??
|
||||
'e6fa9fa97031ba0c852efe78457922f278a2fbc109752fe18e465337699e9873';
|
||||
}
|
||||
|
||||
static String get sentryDsn {
|
||||
return dotenv.env["SENTRY_DSN"] ??
|
||||
return appConfigOverride?.sentryDsn ??
|
||||
dotenv.env["SENTRY_DSN"] ??
|
||||
'https://c2fd19ab2cdc4ebb939a32d01c0e9fa1@o225078.ingest.sentry.io/1376295';
|
||||
}
|
||||
|
||||
static String get rcGoogleKey {
|
||||
return dotenv.env["RC_GOOGLE_KEY"] ?? 'goog_paQMrzFKGzuWZvcMTPkkvIsifJe';
|
||||
return appConfigOverride?.rcGoogleKey ??
|
||||
dotenv.env["RC_GOOGLE_KEY"] ??
|
||||
'goog_paQMrzFKGzuWZvcMTPkkvIsifJe';
|
||||
}
|
||||
|
||||
static String get rcIosKey {
|
||||
return dotenv.env["RC_IOS_KEY"] ?? 'appl_DUPqnxuLjkBLzhBPTWeDjqNENuv';
|
||||
}
|
||||
|
||||
// This is a public key
|
||||
static String get rcStripeKey {
|
||||
return dotenv.env["RC_STRIPE_KEY"] ?? 'strp_YWZxWUeEfvagiefDNoofinaRCOl';
|
||||
return appConfigOverride?.rcIosKey ??
|
||||
dotenv.env["RC_IOS_KEY"] ??
|
||||
'appl_DUPqnxuLjkBLzhBPTWeDjqNENuv';
|
||||
}
|
||||
|
||||
static String get rcOfferingName {
|
||||
return dotenv.env["RC_OFFERING_NAME"] ?? 'default';
|
||||
return appConfigOverride?.rcOfferingName ??
|
||||
dotenv.env["RC_OFFERING_NAME"] ??
|
||||
'default';
|
||||
}
|
||||
|
||||
static String get stripeManagementUrl {
|
||||
return dotenv.env["STRIPE_MANAGEMENT_LINK"] ??
|
||||
return appConfigOverride?.stripeManagementUrl ??
|
||||
dotenv.env["STRIPE_MANAGEMENT_LINK"] ??
|
||||
'https://billing.stripe.com/p/login/dR6dSkf5p6rBc4EcMM';
|
||||
}
|
||||
|
||||
static String get supportSpaceId {
|
||||
return isStaging
|
||||
? '!gqSNSkvwTpgumyjLsV:staging.pangea.chat'
|
||||
: '!MvJoWwKJErvFuTYOdq:pangea.chat';
|
||||
}
|
||||
|
||||
static String get supportUserId {
|
||||
return isStaging ? '@support:staging.pangea.chat' : '@support:pangea.chat';
|
||||
return synapseURL.contains('staging')
|
||||
? '@support:staging.pangea.chat'
|
||||
: '@support:pangea.chat';
|
||||
}
|
||||
|
||||
static String? get botName {
|
||||
return dotenv.env["BOT_NAME"];
|
||||
return appConfigOverride?.botName ?? dotenv.env["BOT_NAME"];
|
||||
}
|
||||
|
||||
static final GetStorage appConfigurationStorage = GetStorage('env_override');
|
||||
|
||||
static Future<List<AppConfigOverride>> getAppConfigOverrides() async {
|
||||
if (!isStagingEnvironment) {
|
||||
return [];
|
||||
}
|
||||
|
||||
List<dynamic> data = [];
|
||||
try {
|
||||
final String jsonString = await rootBundle.loadString('assets/envs.json');
|
||||
data = jsonDecode(jsonString);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {},
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
final List<AppConfigOverride> overrides = [];
|
||||
for (final entry in data) {
|
||||
if (entry is! Map<String, dynamic>) {
|
||||
ErrorHandler.logError(
|
||||
e: Exception("Invalid entry in envs.json"),
|
||||
s: StackTrace.current,
|
||||
data: entry,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final override = AppConfigOverride.fromJson(entry);
|
||||
overrides.add(override);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: entry,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return overrides;
|
||||
}
|
||||
|
||||
static AppConfigOverride? get appConfigOverride {
|
||||
final entry = appConfigurationStorage.read(PLocalKey.appConfigOverride);
|
||||
if (entry == null) return null;
|
||||
try {
|
||||
return AppConfigOverride.fromJson(entry);
|
||||
} catch (e) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: StackTrace.current,
|
||||
data: entry,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> setAppConfigOverride(AppConfigOverride? override) async {
|
||||
appConfigurationStorage.write(
|
||||
PLocalKey.appConfigOverride,
|
||||
override?.toJson(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppConfigOverride {
|
||||
final String? environment;
|
||||
final String? frontendURL;
|
||||
final String? synapseURL;
|
||||
final String? homeServer;
|
||||
final String? choreoApi;
|
||||
final String? choreoApiKey;
|
||||
final String? sentryDsn;
|
||||
final String? rcGoogleKey;
|
||||
final String? rcIosKey;
|
||||
final String? rcOfferingName;
|
||||
final String? stripeManagementUrl;
|
||||
final String? botName;
|
||||
|
||||
const AppConfigOverride({
|
||||
this.environment,
|
||||
this.frontendURL,
|
||||
this.synapseURL,
|
||||
this.homeServer,
|
||||
this.choreoApi,
|
||||
this.choreoApiKey,
|
||||
this.sentryDsn,
|
||||
this.rcGoogleKey,
|
||||
this.rcIosKey,
|
||||
this.rcOfferingName,
|
||||
this.stripeManagementUrl,
|
||||
this.botName,
|
||||
});
|
||||
|
||||
static AppConfigOverride fromJson(Map<String, dynamic> json) {
|
||||
return AppConfigOverride(
|
||||
environment: json['environment'] as String?,
|
||||
frontendURL: json['frontendURL'] as String?,
|
||||
synapseURL: json['synapseURL'] as String?,
|
||||
homeServer: json['homeServer'] as String?,
|
||||
choreoApi: json['choreoApi'] as String?,
|
||||
choreoApiKey: json['choreoApiKey'] as String?,
|
||||
sentryDsn: json['sentryDsn'] as String?,
|
||||
rcGoogleKey: json['rcGoogleKey'] as String?,
|
||||
rcIosKey: json['rcIosKey'] as String?,
|
||||
rcOfferingName: json['rcOfferingName'] as String?,
|
||||
stripeManagementUrl: json['stripeManagementUrl'] as String?,
|
||||
botName: json['botName'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'environment': environment,
|
||||
'frontendURL': frontendURL,
|
||||
'synapseURL': synapseURL,
|
||||
'homeServer': homeServer,
|
||||
'choreoApi': choreoApi,
|
||||
'choreoApiKey': choreoApiKey,
|
||||
'sentryDsn': sentryDsn,
|
||||
'rcGoogleKey': rcGoogleKey,
|
||||
'rcIosKey': rcIosKey,
|
||||
'rcOfferingName': rcOfferingName,
|
||||
'stripeManagementUrl': stripeManagementUrl,
|
||||
'botName': botName,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return environment.hashCode ^
|
||||
frontendURL.hashCode ^
|
||||
synapseURL.hashCode ^
|
||||
homeServer.hashCode ^
|
||||
choreoApi.hashCode ^
|
||||
choreoApiKey.hashCode ^
|
||||
sentryDsn.hashCode ^
|
||||
rcGoogleKey.hashCode ^
|
||||
rcIosKey.hashCode ^
|
||||
rcOfferingName.hashCode ^
|
||||
stripeManagementUrl.hashCode ^
|
||||
botName.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is! AppConfigOverride) return false;
|
||||
return environment == other.environment &&
|
||||
frontendURL == other.frontendURL &&
|
||||
synapseURL == other.synapseURL &&
|
||||
homeServer == other.homeServer &&
|
||||
choreoApi == other.choreoApi &&
|
||||
choreoApiKey == other.choreoApiKey &&
|
||||
sentryDsn == other.sentryDsn &&
|
||||
rcGoogleKey == other.rcGoogleKey &&
|
||||
rcIosKey == other.rcIosKey &&
|
||||
rcOfferingName == other.rcOfferingName &&
|
||||
stripeManagementUrl == other.stripeManagementUrl &&
|
||||
botName == other.botName;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@ class PLocalKey {
|
|||
static const String justInputtedCode = 'justInputtedCode';
|
||||
static const String availableSubscriptionInfo = 'availableSubscriptionInfo';
|
||||
static const String showedUpdateDialog = 'showedUpdateDialog';
|
||||
static const String appConfigOverride = 'appConfigOverride';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,17 +107,35 @@ class PangeaController {
|
|||
_logOutfromPangea() {
|
||||
debugPrint("Pangea logout");
|
||||
GoogleAnalytics.logout();
|
||||
_clearCachedData();
|
||||
clearCache();
|
||||
}
|
||||
|
||||
void _clearCachedData() {
|
||||
GetStorage('mode_list_storage').erase();
|
||||
GetStorage('activity_plan_storage').erase();
|
||||
GetStorage('bookmarked_activities').erase();
|
||||
GetStorage('objective_list_storage').erase();
|
||||
GetStorage('topic_list_storage').erase();
|
||||
GetStorage('lemma_storage').erase();
|
||||
GetStorage().erase();
|
||||
static final List<String> _storageKeys = [
|
||||
'mode_list_storage',
|
||||
'activity_plan_storage',
|
||||
'bookmarked_activities',
|
||||
'objective_list_storage',
|
||||
'topic_list_storage',
|
||||
'activity_plan_search_storage',
|
||||
"analytics_storage",
|
||||
"version_storage",
|
||||
'lemma_storage',
|
||||
'svg_cache',
|
||||
'morphs_storage',
|
||||
'morph_meaning_storage',
|
||||
'practice_record_cache',
|
||||
'practice_selection_cache',
|
||||
'class_storage',
|
||||
'subscription_storage',
|
||||
'vocab_storage',
|
||||
];
|
||||
|
||||
Future<void> clearCache() async {
|
||||
final List<Future<void>> futures = [];
|
||||
for (final key in _storageKeys) {
|
||||
futures.add(GetStorage(key).erase());
|
||||
}
|
||||
await Future.wait(futures);
|
||||
}
|
||||
|
||||
Future<void> checkHomeServerAction() async {
|
||||
|
|
@ -339,7 +357,7 @@ class PangeaController {
|
|||
_languageStream ??= userController.stateStream.listen((update) {
|
||||
if (update is Map<String, dynamic> &&
|
||||
update['prev_target_lang'] != null) {
|
||||
_clearCachedData();
|
||||
clearCache();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ class PApiUrls {
|
|||
static String subscriptionPrefix = "/subscription";
|
||||
static String accountPrefix = "/account";
|
||||
|
||||
static String choreoEndpoint =
|
||||
static String get choreoEndpoint =>
|
||||
"${Environment.choreoApi}${PApiUrls.choreoPrefix}";
|
||||
static String subscriptionEndpoint =
|
||||
static String get subscriptionEndpoint =>
|
||||
"${Environment.choreoApi}${PApiUrls.subscriptionPrefix}";
|
||||
static String accountEndpoint =
|
||||
static String get accountEndpoint =>
|
||||
"${Environment.choreoApi}${PApiUrls.accountPrefix}";
|
||||
|
||||
/// ---------------------- Util --------------------------------------
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class ErrorHandler {
|
|||
options.debug = kDebugMode;
|
||||
options.environment = kDebugMode
|
||||
? "debug"
|
||||
: Environment.isStaging
|
||||
: Environment.isStagingEnvironment
|
||||
? "staging"
|
||||
: "productionC";
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,15 +3,57 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/pangea_login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/app_config_dialog.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/full_width_button.dart';
|
||||
|
||||
class LoginOrSignupView extends StatelessWidget {
|
||||
class LoginOrSignupView extends StatefulWidget {
|
||||
const LoginOrSignupView({super.key});
|
||||
|
||||
@override
|
||||
State<LoginOrSignupView> createState() => LoginOrSignupViewState();
|
||||
}
|
||||
|
||||
class LoginOrSignupViewState extends State<LoginOrSignupView> {
|
||||
List<AppConfigOverride> _overrides = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadOverrides();
|
||||
}
|
||||
|
||||
Future<void> _loadOverrides() async {
|
||||
final overrides = await Environment.getAppConfigOverrides();
|
||||
if (mounted) {
|
||||
setState(() => _overrides = overrides);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setEnvironment() async {
|
||||
if (_overrides.isEmpty) return;
|
||||
|
||||
final resp = await showDialog<AppConfigOverride?>(
|
||||
context: context,
|
||||
builder: (context) => AppConfigDialog(overrides: _overrides),
|
||||
);
|
||||
|
||||
await Environment.setAppConfigOverride(resp);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PangeaLoginScaffold(
|
||||
actions: Environment.isStagingEnvironment && _overrides.isNotEmpty
|
||||
? [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
onPressed: _setEnvironment,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
children: [
|
||||
FullWidthButton(
|
||||
title: L10n.of(context).createAnAccount,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class PangeaLoginScaffold extends StatelessWidget {
|
|||
final List<Widget> children;
|
||||
final bool showAppName;
|
||||
final AppBar? customAppBar;
|
||||
final List<Widget>? actions;
|
||||
|
||||
const PangeaLoginScaffold({
|
||||
required this.children,
|
||||
|
|
@ -21,6 +22,7 @@ class PangeaLoginScaffold extends StatelessWidget {
|
|||
this.mainAssetUrl,
|
||||
this.showAppName = true,
|
||||
this.customAppBar,
|
||||
this.actions,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -32,6 +34,7 @@ class PangeaLoginScaffold extends StatelessWidget {
|
|||
appBar: customAppBar ??
|
||||
AppBar(
|
||||
toolbarHeight: isColumnMode ? null : 40.0,
|
||||
actions: actions,
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
|
|
|
|||
95
lib/pangea/login/widgets/app_config_dialog.dart
Normal file
95
lib/pangea/login/widgets/app_config_dialog.dart
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
|
||||
class AppConfigDialog extends StatefulWidget {
|
||||
final List<AppConfigOverride> overrides;
|
||||
const AppConfigDialog({
|
||||
super.key,
|
||||
required this.overrides,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AppConfigDialog> createState() => AppConfigDialogState();
|
||||
}
|
||||
|
||||
class AppConfigDialogState extends State<AppConfigDialog> {
|
||||
AppConfigOverride? selectedOverride;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedOverride = Environment.appConfigOverride;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog.adaptive(
|
||||
title: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 256),
|
||||
child: Text(
|
||||
L10n.of(context).addEnvironmentOverride,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
content: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 256,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...widget.overrides.map((override) {
|
||||
return RadioListTile<AppConfigOverride?>.adaptive(
|
||||
title: Text(
|
||||
override.environment ?? L10n.of(context).unkDisplayName,
|
||||
),
|
||||
value: override,
|
||||
groupValue: selectedOverride,
|
||||
onChanged: (override) {
|
||||
setState(() {
|
||||
selectedOverride = override;
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList()
|
||||
..insert(
|
||||
0,
|
||||
RadioListTile<AppConfigOverride?>.adaptive(
|
||||
title: Text(L10n.of(context).defaultOption),
|
||||
value: null,
|
||||
groupValue: selectedOverride,
|
||||
onChanged: (override) {
|
||||
setState(() {
|
||||
selectedOverride = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
AdaptiveDialogAction(
|
||||
bigButtons: true,
|
||||
onPressed: () => Navigator.of(context).pop(selectedOverride),
|
||||
child: Text(L10n.of(context).submit),
|
||||
),
|
||||
AdaptiveDialogAction(
|
||||
bigButtons: true,
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text(L10n.of(context).close),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ enum SubscriptionStatus {
|
|||
}
|
||||
|
||||
class SubscriptionController extends BaseController {
|
||||
static final GetStorage subscriptionBox = GetStorage("subscription_storage");
|
||||
late PangeaController _pangeaController;
|
||||
|
||||
CurrentSubscriptionInfo? currentSubscriptionInfo;
|
||||
|
|
@ -81,7 +82,6 @@ class SubscriptionController extends BaseController {
|
|||
await initialize();
|
||||
}
|
||||
|
||||
final GetStorage subscriptionBox = GetStorage("subscription_storage");
|
||||
Future<void> _initialize() async {
|
||||
try {
|
||||
if (_userID == null) {
|
||||
|
|
@ -374,6 +374,37 @@ class SubscriptionController extends BaseController {
|
|||
String? get defaultManagementURL =>
|
||||
currentSubscriptionInfo?.currentSubscription
|
||||
?.defaultManagementURL(availableSubscriptionInfo?.appIds);
|
||||
|
||||
Future<void> setCachedSubscriptionInfo(
|
||||
AvailableSubscriptionsInfo info,
|
||||
) =>
|
||||
subscriptionBox.write(
|
||||
PLocalKey.availableSubscriptionInfo,
|
||||
info.toJson(),
|
||||
);
|
||||
|
||||
Future<AvailableSubscriptionsInfo?> getCachedSubscriptionInfo() async {
|
||||
final entry = subscriptionBox.read(
|
||||
PLocalKey.availableSubscriptionInfo,
|
||||
);
|
||||
if (entry is! Map<String, dynamic>) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final resp = AvailableSubscriptionsInfo.fromJson(entry);
|
||||
return resp.lastUpdated == null ? null : resp;
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"entry": entry,
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum SubscriptionDuration {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/common/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/subscription/repo/subscription_repo.dart';
|
||||
|
|
@ -74,9 +73,6 @@ class AvailableSubscriptionsInfo {
|
|||
List<SubscriptionDetails>? allProducts;
|
||||
DateTime? lastUpdated;
|
||||
|
||||
final subscriptionBox =
|
||||
MatrixState.pangeaController.subscriptionController.subscriptionBox;
|
||||
|
||||
AvailableSubscriptionsInfo({
|
||||
this.appIds,
|
||||
this.allProducts,
|
||||
|
|
@ -84,7 +80,8 @@ class AvailableSubscriptionsInfo {
|
|||
});
|
||||
|
||||
Future<void> setAvailableSubscriptions() async {
|
||||
final cachedInfo = _getCachedSubscriptionInfo();
|
||||
final cachedInfo = await MatrixState.pangeaController.subscriptionController
|
||||
.getCachedSubscriptionInfo();
|
||||
appIds ??= cachedInfo?.appIds ?? await SubscriptionRepo.getAppIds();
|
||||
allProducts ??=
|
||||
cachedInfo?.allProducts ?? await SubscriptionRepo.getAllProducts();
|
||||
|
|
@ -102,11 +99,8 @@ class AvailableSubscriptionsInfo {
|
|||
|
||||
Future<void> _cacheSubscriptionInfo() async {
|
||||
try {
|
||||
final json = toJson();
|
||||
await subscriptionBox.write(
|
||||
PLocalKey.availableSubscriptionInfo,
|
||||
json,
|
||||
);
|
||||
MatrixState.pangeaController.subscriptionController
|
||||
.setCachedSubscriptionInfo(this);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
|
|
@ -119,29 +113,6 @@ class AvailableSubscriptionsInfo {
|
|||
}
|
||||
}
|
||||
|
||||
AvailableSubscriptionsInfo? _getCachedSubscriptionInfo() {
|
||||
final json = subscriptionBox.read(
|
||||
PLocalKey.availableSubscriptionInfo,
|
||||
);
|
||||
if (json is! Map<String, dynamic>) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final resp = AvailableSubscriptionsInfo.fromJson(json);
|
||||
return resp.lastUpdated == null ? null : resp;
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"json": json,
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
factory AvailableSubscriptionsInfo.fromJson(Map<String, dynamic> json) {
|
||||
if (!json.containsKey('app_ids') || !json.containsKey('all_products')) {
|
||||
throw "Cached subscription info is missing required fields";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue