143 lines
4.4 KiB
Dart
143 lines
4.4 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
|
|
enum GroupCategory {
|
|
general('general', 'General', Icons.grid_view, Color(0xFF607D8B)),
|
|
hobby('hobby', 'Hobby', Icons.palette, Color(0xFF9C27B0)),
|
|
sports('sports', 'Sports', Icons.sports, Color(0xFF4CAF50)),
|
|
professional('professional', 'Professional', Icons.business_center, Color(0xFF2196F3)),
|
|
localBusiness('local_business', 'Local Business', Icons.storefront, Color(0xFFFF9800)),
|
|
support('support', 'Support', Icons.favorite, Color(0xFFE91E63)),
|
|
education('education', 'Education', Icons.school, Color(0xFF3F51B5));
|
|
|
|
final String value;
|
|
final String displayName;
|
|
final IconData icon;
|
|
final Color color;
|
|
const GroupCategory(this.value, this.displayName, this.icon, this.color);
|
|
|
|
static GroupCategory fromString(String s) {
|
|
return GroupCategory.values.firstWhere((c) => c.value == s, orElse: () => GroupCategory.general);
|
|
}
|
|
}
|
|
|
|
/// Represents a Sojorn Cluster — either a public geo-cluster or a private capsule.
|
|
class Cluster {
|
|
final String id;
|
|
final String name;
|
|
final String description;
|
|
final String type; // 'geo', 'public_geo', 'private_capsule'
|
|
final String privacy;
|
|
final double? lat;
|
|
final double? lng;
|
|
final int radiusMeters;
|
|
final String? avatarUrl;
|
|
final int memberCount;
|
|
final bool isEncrypted;
|
|
final ClusterSettings settings;
|
|
final GroupCategory category;
|
|
final int keyVersion;
|
|
final DateTime createdAt;
|
|
|
|
Cluster({
|
|
required this.id,
|
|
required this.name,
|
|
this.description = '',
|
|
required this.type,
|
|
this.privacy = 'public',
|
|
this.lat,
|
|
this.lng,
|
|
this.radiusMeters = 5000,
|
|
this.avatarUrl,
|
|
this.memberCount = 0,
|
|
this.isEncrypted = false,
|
|
this.settings = const ClusterSettings(),
|
|
this.category = GroupCategory.general,
|
|
this.keyVersion = 1,
|
|
required this.createdAt,
|
|
});
|
|
|
|
bool get isPublic => type == 'geo' || type == 'public_geo';
|
|
bool get isCapsule => type == 'private_capsule' && isEncrypted;
|
|
|
|
factory Cluster.fromJson(Map<String, dynamic> json) {
|
|
ClusterSettings settings = const ClusterSettings();
|
|
if (json['settings'] != null) {
|
|
final raw = json['settings'];
|
|
if (raw is String) {
|
|
settings = ClusterSettings.fromJson(jsonDecode(raw));
|
|
} else if (raw is Map<String, dynamic>) {
|
|
settings = ClusterSettings.fromJson(raw);
|
|
}
|
|
}
|
|
|
|
return Cluster(
|
|
id: json['id'] as String,
|
|
name: json['name'] as String? ?? '',
|
|
description: json['description'] as String? ?? '',
|
|
type: json['type'] as String? ?? 'geo',
|
|
privacy: json['privacy'] as String? ?? 'public',
|
|
lat: (json['lat'] as num?)?.toDouble(),
|
|
lng: (json['long'] as num?)?.toDouble() ?? (json['lng'] as num?)?.toDouble(),
|
|
radiusMeters: json['radius_meters'] as int? ?? 5000,
|
|
avatarUrl: json['avatar_url'] as String?,
|
|
memberCount: json['member_count'] as int? ?? 0,
|
|
isEncrypted: json['is_encrypted'] as bool? ?? false,
|
|
settings: settings,
|
|
category: GroupCategory.fromString(json['category'] as String? ?? 'general'),
|
|
keyVersion: json['key_version'] as int? ?? 1,
|
|
createdAt: DateTime.parse(json['created_at'] as String),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Feature flags for a cluster
|
|
class ClusterSettings {
|
|
final bool chat;
|
|
final bool forum;
|
|
final bool files;
|
|
|
|
const ClusterSettings({this.chat = true, this.forum = true, this.files = false});
|
|
|
|
factory ClusterSettings.fromJson(Map<String, dynamic> json) => ClusterSettings(
|
|
chat: json['chat'] as bool? ?? true,
|
|
forum: json['forum'] as bool? ?? true,
|
|
files: json['files'] as bool? ?? false,
|
|
);
|
|
|
|
Map<String, dynamic> toJson() => {'chat': chat, 'forum': forum, 'files': files};
|
|
}
|
|
|
|
/// A decrypted capsule entry (after client-side decryption)
|
|
class DecryptedCapsuleEntry {
|
|
final String id;
|
|
final String groupId;
|
|
final String authorId;
|
|
final String authorHandle;
|
|
final String authorDisplayName;
|
|
final String authorAvatarUrl;
|
|
final String dataType; // chat, forum_post, document, image
|
|
final String? replyToId;
|
|
final DateTime createdAt;
|
|
|
|
// Decrypted content
|
|
final String? title;
|
|
final String? body;
|
|
final String? imageUrl;
|
|
|
|
DecryptedCapsuleEntry({
|
|
required this.id,
|
|
required this.groupId,
|
|
required this.authorId,
|
|
this.authorHandle = '',
|
|
this.authorDisplayName = '',
|
|
this.authorAvatarUrl = '',
|
|
required this.dataType,
|
|
this.replyToId,
|
|
required this.createdAt,
|
|
this.title,
|
|
this.body,
|
|
this.imageUrl,
|
|
});
|
|
}
|