1547 level indicator for all users (#1722)
* feat: publicly viewable target language and level indicator --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
5347b4764f
commit
b98f2d3283
15 changed files with 370 additions and 110 deletions
|
|
@ -349,13 +349,16 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
});
|
||||
|
||||
_levelSubscription = pangeaController.getAnalytics.analyticsStream.stream
|
||||
.where((update) => update.levelUp)
|
||||
_levelSubscription = pangeaController.getAnalytics.stateStream
|
||||
.where(
|
||||
(update) =>
|
||||
update is Map<String, dynamic> && update['level_up'] != null,
|
||||
)
|
||||
.listen(
|
||||
(update) => Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() => LevelUpUtil.showLevelUpDialog(
|
||||
pangeaController.getAnalytics.constructListModel.level,
|
||||
update['level_up'],
|
||||
context,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/user/widgets/public_level_indicator.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
|
|
@ -194,6 +195,9 @@ class UserBottomSheetView extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
// #Pangea
|
||||
PublicLevelIndicator(userId: userId),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/message_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.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';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
|
||||
/// A minimized version of AnalyticsController that get the logged in user's analytics
|
||||
class GetAnalyticsController {
|
||||
class GetAnalyticsController extends BaseController {
|
||||
late PangeaController _pangeaController;
|
||||
late MessageAnalyticsController perMessage;
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ class GetAnalyticsController {
|
|||
|
||||
ConstructListModel constructListModel = ConstructListModel(uses: []);
|
||||
Completer<void> initCompleter = Completer<void>();
|
||||
bool _initializing = false;
|
||||
|
||||
GetAnalyticsController(PangeaController pangeaController) {
|
||||
_pangeaController = pangeaController;
|
||||
|
|
@ -69,7 +71,8 @@ class GetAnalyticsController {
|
|||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (initCompleter.isCompleted) return;
|
||||
if (_initializing || initCompleter.isCompleted) return;
|
||||
_initializing = true;
|
||||
|
||||
try {
|
||||
_client.updateAnalyticsRoomVisibility();
|
||||
|
|
@ -100,10 +103,12 @@ class GetAnalyticsController {
|
|||
} finally {
|
||||
_updateAnalyticsStream();
|
||||
if (!initCompleter.isCompleted) initCompleter.complete();
|
||||
_initializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all cached analytics data.
|
||||
@override
|
||||
void dispose() {
|
||||
constructListModel.dispose();
|
||||
_analyticsUpdateSubscription?.cancel();
|
||||
|
|
@ -124,19 +129,21 @@ class GetAnalyticsController {
|
|||
if (analyticsUpdate.type == AnalyticsUpdateType.server) {
|
||||
await _getConstructs(forceUpdate: true);
|
||||
}
|
||||
_updateAnalyticsStream(
|
||||
origin: analyticsUpdate.origin,
|
||||
levelUp: oldLevel < constructListModel.level,
|
||||
);
|
||||
_updateAnalyticsStream(origin: analyticsUpdate.origin);
|
||||
if (oldLevel < constructListModel.level) _onLevelUp();
|
||||
}
|
||||
|
||||
void _updateAnalyticsStream({
|
||||
bool levelUp = false,
|
||||
AnalyticsUpdateOrigin? origin,
|
||||
}) {
|
||||
analyticsStream.add(
|
||||
AnalyticsStreamUpdate(origin: origin, levelUp: levelUp),
|
||||
}) =>
|
||||
analyticsStream.add(AnalyticsStreamUpdate(origin: origin));
|
||||
|
||||
void _onLevelUp() {
|
||||
_pangeaController.userController.updatePublicProfile(
|
||||
level: constructListModel.level,
|
||||
);
|
||||
|
||||
setState({'level_up': constructListModel.level});
|
||||
}
|
||||
|
||||
/// A local cache of eventIds and construct uses for messages sent since the last update.
|
||||
|
|
@ -209,7 +216,9 @@ class GetAnalyticsController {
|
|||
}) async {
|
||||
// if the user isn't logged in, return an empty list
|
||||
if (_client.userID == null) return [];
|
||||
await _client.roomsLoading;
|
||||
if (_client.prevBatch == null) {
|
||||
await _client.onSync.stream.first;
|
||||
}
|
||||
|
||||
// don't try to get constructs until last updated time has been loaded
|
||||
await _pangeaController.putAnalytics.lastUpdatedCompleter.future;
|
||||
|
|
@ -342,10 +351,8 @@ class AnalyticsCacheEntry {
|
|||
|
||||
class AnalyticsStreamUpdate {
|
||||
final AnalyticsUpdateOrigin? origin;
|
||||
final bool levelUp;
|
||||
|
||||
AnalyticsStreamUpdate({
|
||||
this.origin,
|
||||
this.levelUp = false,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,10 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
await sendLocalAnalyticsToAnalyticsRoom(
|
||||
l2Override: previousL2,
|
||||
);
|
||||
_pangeaController.resetAnalytics();
|
||||
_pangeaController.resetAnalytics().then((_) {
|
||||
final level = _pangeaController.getAnalytics.constructListModel.level;
|
||||
_pangeaController.userController.updatePublicProfile(level: level);
|
||||
});
|
||||
}
|
||||
|
||||
void addDraftUses(
|
||||
|
|
@ -361,6 +364,8 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
String? l2Override,
|
||||
}) async {
|
||||
if (_pangeaController.matrixState.client.userID == null) return;
|
||||
if (_pangeaController.getAnalytics.messagesSinceUpdate.isEmpty) return;
|
||||
|
||||
if (!(_updateCompleter?.isCompleted ?? true)) {
|
||||
await _updateCompleter!.future;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:fluffychat/pangea/common/constants/local.key.dart';
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/network/urls.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -52,7 +53,14 @@ class AppVersionUtil {
|
|||
final currentBuildNumber = packageInfo.buildNumber;
|
||||
|
||||
final accessToken = MatrixState.pangeaController.userController.accessToken;
|
||||
final AppVersionResponse resp = await _getAppVersion(accessToken);
|
||||
|
||||
AppVersionResponse? resp;
|
||||
try {
|
||||
resp = await _getAppVersion(accessToken);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s, data: {});
|
||||
return;
|
||||
}
|
||||
|
||||
final remoteVersion = resp.latestVersion;
|
||||
final remoteBuildNumber = resp.latestBuildNumber;
|
||||
|
|
|
|||
|
|
@ -152,4 +152,7 @@ class ModelKey {
|
|||
static const String latestBuildNumber = "latest_build_number";
|
||||
static const String mandatoryUpdate = "mandatory_update";
|
||||
static const String emoji = "emoji";
|
||||
|
||||
static const String analytics = "analytics";
|
||||
static const String level = "level";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,14 +158,14 @@ class PangeaController {
|
|||
case LoginState.loggedOut:
|
||||
case LoginState.softLoggedOut:
|
||||
// Reset cached analytics data
|
||||
MatrixState.pangeaController.putAnalytics.dispose();
|
||||
MatrixState.pangeaController.getAnalytics.dispose();
|
||||
putAnalytics.dispose();
|
||||
getAnalytics.dispose();
|
||||
_languageStream?.cancel();
|
||||
break;
|
||||
case LoginState.loggedIn:
|
||||
// Initialize analytics data
|
||||
MatrixState.pangeaController.putAnalytics.initialize();
|
||||
MatrixState.pangeaController.getAnalytics.initialize();
|
||||
putAnalytics.initialize();
|
||||
getAnalytics.initialize();
|
||||
break;
|
||||
}
|
||||
if (state != LoginState.loggedIn) {
|
||||
|
|
@ -186,7 +186,7 @@ class PangeaController {
|
|||
putAnalytics.dispose();
|
||||
getAnalytics.dispose();
|
||||
putAnalytics.initialize();
|
||||
getAnalytics.initialize();
|
||||
await getAnalytics.initialize();
|
||||
}
|
||||
|
||||
void startChatWithBotIfNotPresent() {
|
||||
|
|
|
|||
|
|
@ -36,4 +36,7 @@ class PangeaEventTypes {
|
|||
/// A record of completion of an activity. There
|
||||
/// can be one per user per activity.
|
||||
static const activityRecord = "pangea.activity_completion";
|
||||
|
||||
/// Profile information related to a user's analytics
|
||||
static const profileAnalytics = "pangea.analytics_profile";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
context: context,
|
||||
future: () async => pangeaController.userController.updateProfile(
|
||||
(_) => _profile,
|
||||
waitForDataInSync: true,
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
|||
|
|
@ -241,7 +241,10 @@ class SignupPageController extends State<SignupPage> {
|
|||
data: {},
|
||||
);
|
||||
}
|
||||
error = (e).toLocalizedString(context);
|
||||
|
||||
if (mounted) {
|
||||
error = (e).toLocalizedString(context);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => loadingSignup = false);
|
||||
|
|
|
|||
|
|
@ -215,6 +215,10 @@ class UserSettingsState extends State<UserSettingsPage> {
|
|||
},
|
||||
waitForDataInSync: true,
|
||||
),
|
||||
_pangeaController.userController.updatePublicProfile(
|
||||
targetLanguage: selectedTargetLanguage,
|
||||
level: 1,
|
||||
),
|
||||
];
|
||||
await Future.wait(updateFuture).timeout(
|
||||
const Duration(seconds: 30),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import 'package:fluffychat/pangea/common/constants/model_keys.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';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/user/models/profile_model.dart';
|
||||
import '../models/user_model.dart';
|
||||
|
||||
/// Controller that manages saving and reading of user/profile information
|
||||
|
|
@ -18,23 +21,26 @@ class UserController extends BaseController {
|
|||
_pangeaController = pangeaController;
|
||||
}
|
||||
|
||||
matrix.Client get client => _pangeaController.matrixState.client;
|
||||
|
||||
/// Convenience function that returns the user ID currently stored in the client.
|
||||
String? get userId => _pangeaController.matrixState.client.userID;
|
||||
String? get userId => client.userID;
|
||||
|
||||
/// Cached version of the user profile, so it doesn't have
|
||||
/// to be read in from client's account data each time it is accessed.
|
||||
Profile? _cachedProfile;
|
||||
|
||||
PublicProfileModel? publicProfile;
|
||||
|
||||
/// Listens for account updates and updates the cached profile
|
||||
StreamSubscription? _profileListener;
|
||||
|
||||
/// Listen for updates to account data in syncs and update the cached profile
|
||||
void addProfileListener() {
|
||||
_profileListener ??= _pangeaController.matrixState.client.onSync.stream
|
||||
_profileListener ??= client.onSync.stream
|
||||
.where((sync) => sync.accountData != null)
|
||||
.listen((sync) {
|
||||
final profileData = _pangeaController
|
||||
.matrixState.client.accountData[ModelKey.userProfile]?.content;
|
||||
final profileData = client.accountData[ModelKey.userProfile]?.content;
|
||||
final Profile? fromAccountData = Profile.fromAccountData(profileData);
|
||||
if (fromAccountData != null) {
|
||||
_cachedProfile = fromAccountData;
|
||||
|
|
@ -50,14 +56,13 @@ class UserController extends BaseController {
|
|||
if (_cachedProfile != null) return _cachedProfile!;
|
||||
|
||||
/// if account data is empty, return an empty profile
|
||||
if (_pangeaController.matrixState.client.accountData.isEmpty) {
|
||||
if (client.accountData.isEmpty) {
|
||||
return Profile.emptyProfile;
|
||||
}
|
||||
|
||||
/// try to get the account data in the up-to-date format
|
||||
final Profile? fromAccountData = Profile.fromAccountData(
|
||||
_pangeaController
|
||||
.matrixState.client.accountData[ModelKey.userProfile]?.content,
|
||||
client.accountData[ModelKey.userProfile]?.content,
|
||||
);
|
||||
|
||||
if (fromAccountData != null) {
|
||||
|
|
@ -85,16 +90,6 @@ class UserController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new profile for the user with the given date of birth.
|
||||
Future<void> createProfile({DateTime? dob}) async {
|
||||
final userSettings = UserSettings(
|
||||
dateOfBirth: dob,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
final newProfile = Profile(userSettings: userSettings);
|
||||
await newProfile.saveProfileData(waitForDataInSync: true);
|
||||
}
|
||||
|
||||
/// A completer for the profile model of a user.
|
||||
Completer<void>? _profileCompleter;
|
||||
|
||||
|
|
@ -136,8 +131,31 @@ class UserController extends BaseController {
|
|||
Future<void> _initialize() async {
|
||||
// wait for account data to load
|
||||
// as long as it's not null, then this we've already migrated the profile
|
||||
if (_pangeaController.matrixState.client.prevBatch == null) {
|
||||
await _pangeaController.matrixState.client.onSync.stream.first;
|
||||
if (client.prevBatch == null) {
|
||||
await client.onSync.stream.first;
|
||||
}
|
||||
|
||||
if (client.userID == null) return;
|
||||
try {
|
||||
final resp = await client.getUserProfile(client.userID!);
|
||||
publicProfile = PublicProfileModel.fromJson(resp.additionalProperties);
|
||||
} catch (e) {
|
||||
// getting a 404 error for some users without pre-existing profile
|
||||
// still want to set other properties, so catch this error
|
||||
publicProfile = PublicProfileModel();
|
||||
}
|
||||
|
||||
// Do not await. This function pulls level from analytics,
|
||||
// so it waits for analytics to finish initializing. Analytics waits for user controller to
|
||||
// finish initializing, so this would cause a deadlock.
|
||||
if (publicProfile!.isEmpty) {
|
||||
_pangeaController.getAnalytics.initCompleter.future
|
||||
.timeout(const Duration(seconds: 10))
|
||||
.then((_) {
|
||||
updatePublicProfile(
|
||||
level: _pangeaController.getAnalytics.constructListModel.level,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,12 +167,36 @@ class UserController extends BaseController {
|
|||
await initialize();
|
||||
}
|
||||
|
||||
Future<void> updatePublicProfile({
|
||||
LanguageModel? targetLanguage,
|
||||
int? level,
|
||||
}) async {
|
||||
targetLanguage ??= _pangeaController.languageController.userL2;
|
||||
if (targetLanguage == null || publicProfile == null) return;
|
||||
|
||||
if (publicProfile!.targetLanguage == targetLanguage &&
|
||||
publicProfile!.languageAnalytics?[targetLanguage]?.level == level) {
|
||||
return;
|
||||
}
|
||||
|
||||
publicProfile!.targetLanguage = targetLanguage;
|
||||
if (level != null) {
|
||||
publicProfile!.setLevel(targetLanguage, level);
|
||||
}
|
||||
|
||||
await client.setUserProfile(
|
||||
client.userID!,
|
||||
PangeaEventTypes.profileAnalytics,
|
||||
publicProfile!.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed.
|
||||
bool needNewJWT(String token) => Jwt.isExpired(token);
|
||||
|
||||
/// Retrieves matrix access token.
|
||||
String get accessToken {
|
||||
final token = _pangeaController.matrixState.client.accessToken;
|
||||
final token = client.accessToken;
|
||||
if (token == null) {
|
||||
throw ("Trying to get accessToken with null token. User is not logged in.");
|
||||
}
|
||||
|
|
@ -251,11 +293,27 @@ class UserController extends BaseController {
|
|||
/// is found.
|
||||
Future<String?> get userEmail async {
|
||||
final List<matrix.ThirdPartyIdentifier>? identifiers =
|
||||
await _pangeaController.matrixState.client.getAccount3PIDs();
|
||||
await client.getAccount3PIDs();
|
||||
final matrix.ThirdPartyIdentifier? email = identifiers?.firstWhereOrNull(
|
||||
(identifier) =>
|
||||
identifier.medium == matrix.ThirdPartyIdentifierMedium.email,
|
||||
);
|
||||
return email?.address;
|
||||
}
|
||||
|
||||
Future<PublicProfileModel> getPublicProfile(String userId) async {
|
||||
try {
|
||||
final resp = await client.getUserProfile(userId);
|
||||
return PublicProfileModel.fromJson(resp.additionalProperties);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
userId: userId,
|
||||
},
|
||||
);
|
||||
return PublicProfileModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
75
lib/pangea/user/models/profile_model.dart
Normal file
75
lib/pangea/user/models/profile_model.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/utils/language_list_util.dart';
|
||||
|
||||
class PublicProfileModel {
|
||||
LanguageModel? targetLanguage;
|
||||
Map<LanguageModel, LanguageAnalyticsProfileEntry>? languageAnalytics;
|
||||
|
||||
PublicProfileModel({this.targetLanguage, this.languageAnalytics});
|
||||
|
||||
factory PublicProfileModel.fromJson(Map<String, dynamic> json) {
|
||||
if (!json.containsKey(PangeaEventTypes.profileAnalytics)) {
|
||||
return PublicProfileModel();
|
||||
}
|
||||
|
||||
final profileJson = json[PangeaEventTypes.profileAnalytics];
|
||||
|
||||
final targetLanguage = profileJson[ModelKey.userTargetLanguage] != null
|
||||
? PangeaLanguage.byLangCode(profileJson[ModelKey.userTargetLanguage])
|
||||
: null;
|
||||
|
||||
final languageAnalytics = <LanguageModel, LanguageAnalyticsProfileEntry>{};
|
||||
if (profileJson[ModelKey.analytics] != null &&
|
||||
profileJson[ModelKey.analytics]!.isNotEmpty) {
|
||||
for (final entry in profileJson[ModelKey.analytics].entries) {
|
||||
final lang = PangeaLanguage.byLangCode(entry.key);
|
||||
if (lang == null) continue;
|
||||
final level = entry.value[ModelKey.level];
|
||||
languageAnalytics[lang] = LanguageAnalyticsProfileEntry(level);
|
||||
}
|
||||
}
|
||||
|
||||
final profile = PublicProfileModel(
|
||||
targetLanguage: targetLanguage,
|
||||
languageAnalytics: languageAnalytics,
|
||||
);
|
||||
return profile;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (targetLanguage != null) {
|
||||
json[ModelKey.userTargetLanguage] = targetLanguage!.langCode;
|
||||
}
|
||||
|
||||
final analytics = {};
|
||||
if (languageAnalytics != null && languageAnalytics!.isNotEmpty) {
|
||||
for (final entry in languageAnalytics!.entries) {
|
||||
analytics[entry.key.langCode] = {ModelKey.level: entry.value.level};
|
||||
}
|
||||
}
|
||||
|
||||
json[ModelKey.analytics] = analytics;
|
||||
return json;
|
||||
}
|
||||
|
||||
bool get isEmpty =>
|
||||
targetLanguage == null &&
|
||||
(languageAnalytics == null || languageAnalytics!.isEmpty);
|
||||
|
||||
void setLevel(LanguageModel language, int level) {
|
||||
languageAnalytics ??= {};
|
||||
languageAnalytics![language] ??= LanguageAnalyticsProfileEntry(0);
|
||||
languageAnalytics![language]!.level = level;
|
||||
}
|
||||
|
||||
int? get level => languageAnalytics?[targetLanguage]?.level;
|
||||
}
|
||||
|
||||
class LanguageAnalyticsProfileEntry {
|
||||
int level;
|
||||
|
||||
LanguageAnalyticsProfileEntry(this.level);
|
||||
}
|
||||
150
lib/pangea/user/widgets/public_level_indicator.dart
Normal file
150
lib/pangea/user/widgets/public_level_indicator.dart
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/user/models/profile_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class PublicLevelIndicator extends StatelessWidget {
|
||||
final String userId;
|
||||
const PublicLevelIndicator({
|
||||
required this.userId,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profileFuture =
|
||||
MatrixState.pangeaController.userController.getPublicProfile(userId);
|
||||
|
||||
return FutureBuilder<PublicProfileModel>(
|
||||
future: profileFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: LinearProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
size: 14,
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
weight: 1000,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
L10n.of(context).oopsSomethingWentWrong,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.hasData &&
|
||||
snapshot.data!.targetLanguage == null &&
|
||||
snapshot.data!.level == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (snapshot.data?.targetLanguage != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
size: 14,
|
||||
Icons.language,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
weight: 1000,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
snapshot.data!.targetLanguage!.displayName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
if (snapshot.data?.level != null)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
color: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: AppConfig.gold,
|
||||
radius: 8,
|
||||
child: Icon(
|
||||
size: 12,
|
||||
Icons.star,
|
||||
color: Theme.of(context).colorScheme.surfaceBright,
|
||||
weight: 1000,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
L10n.of(context).levelShort(snapshot.data!.level!),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
66
pubspec.lock
66
pubspec.lock
|
|
@ -153,14 +153,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
base58check:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: base58check
|
||||
sha256: "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
blurhash_dart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -193,14 +185,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
canonical_json:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: canonical_json
|
||||
sha256: d6be1dd66b420c6ac9f42e3693e09edf4ff6edfee26cb4c28c1c019fdb8c0c15
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
characters:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -433,14 +417,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.9"
|
||||
enhanced_enum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: enhanced_enum
|
||||
sha256: "074c5a8b9664799ca91e1e8b68003b8694cb19998671cbafd9c7779c13fcdecf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1119,14 +1095,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.4"
|
||||
html_unescape:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html_unescape
|
||||
sha256: "15362d7a18f19d7b742ef8dcb811f5fd2a2df98db9f80ea393c075189e0b61e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1465,7 +1433,7 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "03be44cc13cb15a1b6fa586589eb8c243979d381"
|
||||
resolved-ref: e77d79ff2a9b91d5cd950a0742be2f74781cb19f
|
||||
url: "https://github.com/pangeachat/matrix-dart-sdk.git"
|
||||
source: git
|
||||
version: "0.37.0"
|
||||
|
|
@ -1525,14 +1493,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
olm:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: olm
|
||||
sha256: "3306bf534ceb914fd148b3b4a3d603fb5e067b2e6da8304025b47c24cfdf6b46"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
open_file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1885,14 +1845,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
random_string:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: random_string
|
||||
sha256: "03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
receive_sharing_intent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -2005,14 +1957,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
sdp_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sdp_transform
|
||||
sha256: "73e412a5279a5c2de74001535208e20fff88f225c9a4571af0f7146202755e45"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.2"
|
||||
sentry:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -2490,14 +2434,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
unorm_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unorm_dart
|
||||
sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue