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>pull/1593/head
parent
5347b4764f
commit
b98f2d3283
@ -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);
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue