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