This commit is contained in:
2026-02-13 15:53:22 +01:00
commit 25271189b5
639 changed files with 49083 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
import 'package:hiddingsel_app/appflow/model/images.dart';
import 'package:hiddingsel_app/appflow/model/represented_organisations.dart';
import 'package:hiddingsel_app/services/storage.dart';
import 'package:html/parser.dart' show parse;
import '../../todo/WebConnector.dart';
import 'event_organisations.dart';
import 'interfaces.dart';
class ArticleModel with IsShareable, JsonEncodable {
final int id;
final String title;
final String htmlContent;
final DateTime publishedAt;
final ImageModel image;
final Uri url;
final List<RepresentedOrganisationModel> categories;
String get content => WebViewHelper.getHtmlContentWithoutMedia(htmlContent);
List<ImageModel> get otherImages => WebViewHelper.getImageMedia(htmlContent)
.map((url) => ImageModel(url)).toList();
ArticleModel.withImageModel(this.id, this.title, this.htmlContent, this.publishedAt,
this.image, this.url,
{categories, otherImages})
: this.categories = categories ?? [];
ArticleModel(this.id, this.title, this.htmlContent, this.publishedAt,
String thumbnailImageResource, this.url,
{categories, otherImages})
: this.image = ImageModel(thumbnailImageResource), this.categories = categories ?? List<RepresentedOrganisationModel>.empty();
factory ArticleModel.fromWordpressJson(Map<String, dynamic> json) => ArticleModel(
json['id'] as int,
parse(json['title']['rendered'] as String).documentElement!.text,
json['content']['rendered'] as String,
DateTime.parse(json['date'] as String),
json['_embedded']['wp:featuredmedia'][0]['source_url'] as String,
Uri.parse(json['link'] as String),
categories: (json['categories'] as List<dynamic>)
.cast<int>()
.map((id) => RepresentedOrganisationModel.fromWordpressId(id))
.where((e) => e != null)
.cast<RepresentedOrganisationModel>()
.toList(),
);
factory ArticleModel.fromJson(Map<String, dynamic> json) => ArticleModel(
json['id'] as int,
json['title'] as String,
json['htmlContent'] as String,
DateTime.parse(json['publishedAt'] as String),
json['image'] as String,
Uri.parse(json['url'] as String),
categories: (json['categories'] as List<dynamic>)
.cast<String>()
.map((id) => EventOrganisationModel.fromId(id))
.where((e) => e != null)
.cast<RepresentedOrganisationModel>()
.toList(),
otherImages: (json['otherImages'] as List<dynamic>)
.cast<String>()
.map((resouce) => ImageModel(resouce))
.toList(),
);
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'htmlContent': htmlContent,
'publishedAt': publishedAt.toIso8601String(),
'image': image.resource,
'url': url.toString(),
'categories': categories.map((c) => c.id).toList(),
'otherImages': otherImages.map((i) => i.resource).toList(),
};
@override
bool operator ==(Object other) =>
other is ArticleModel &&
runtimeType == other.runtimeType &&
id == other.id;
@override
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/cupertino.dart';
import 'package:get_it/get_it.dart';
import 'package:hiddingsel_app/appflow/model/images.dart';
import 'package:hiddingsel_app/appflow/model/persons.dart';
import '../../constants/constant.dart';
import 'contacts.dart';
import 'interfaces.dart';
import 'package:hiddingsel_app/appflow/model/topics.dart';
class CompanyModel extends PushNotificationTopicModel
with Contact, Person {
final ImageModel image;
final PersonModel person;
final ContactModel contact;
final String? products;
final String? connectionToHiddingsel;
InlineSpan get text => TextSpan(text: 'Produkte/Dienstleistungen:\n', style: UITheme.theme.textTheme.headlineSmall, children:
[
TextSpan(text: '$products\n', style: UITheme.theme.textTheme.bodyLarge),
TextSpan(text: 'Was verbindet Euch mit Hiddingsel?\n', style: UITheme.theme.textTheme.headlineSmall),
TextSpan(text: '$connectionToHiddingsel\n', style: UITheme.theme.textTheme.bodyLarge),],);
const CompanyModel.withImageModel(id, name, this.image, this.person, this.contact, this.products, this.connectionToHiddingsel) : super(id, name);
CompanyModel(id, name, String imageResource, this.person, this.contact, this.products, this.connectionToHiddingsel) : this.image = ImageModel(imageResource), super(id, name);
factory CompanyModel.fromJson(Map<String, dynamic> json) {
return CompanyModel(
json['id'] as String,
json['name'] as String,
json['image'] as String,
PersonModel.fromJson(json['person'] as Map<String, dynamic>),
ContactModel.fromJson(json['contact'] as Map<String, dynamic>),
json['products'] as String?,
json['connectionToHiddingsel'] as String?,
);
}
static final List<CompanyModel> values = GetIt.I<List<CompanyModel>>()..sort((a, b) => a.name.compareTo(b.name));
bool operator ==(Object other) =>
other is CompanyModel &&
runtimeType == other.runtimeType &&
id == other.id;
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,55 @@
import 'package:hiddingsel_app/appflow/model/interfaces.dart';
import 'package:hiddingsel_app/appflow/model/location.dart';
class ContactModel with IsSearchable{
final String? phone;
final String? mail;
final LocationModel? location;
final Uri? website;
final String? facebook;
final String? instagram;
final String? twitter;
get tags => [phone, mail, location?.address, website?.host, facebook, instagram].where((e) => e != null).toList();
const ContactModel(
{this.phone,
this.mail,
this.location,
this.website,
this.facebook,
this.instagram,
this.twitter
});
factory ContactModel.fromJson(Map<String, dynamic> json) {
return ContactModel(
phone: (json['phone'] as String?) != null ? json['phone'] as String: null,
mail: (json['mail'] as String?) != null ? json['mail'] as String: null,
location: (json['location'] as Map<String, dynamic>?) != null
? LocationModel.fromJson(json['location'] as Map<String, dynamic>)
: null,
website: (json['website'] as String?) != null
? Uri.parse(json['website'] as String)
: null,
facebook: (json['facebook'] as String?) != null
? json['facebook'] as String
: null,
instagram: (json['instagram'] as String?) != null
? json['instagram'] as String
: null,
twitter: (json['twitter'] as String?) != null
? json['twitter'] as String
: null,
);
}
toJson() =>{
'phone': phone,
'mail': mail,
'location': location?.toJson(),
'website': website?.toString(),
'facebook': facebook,
'instagram': instagram,
'twitter': twitter,
};
}

View File

@@ -0,0 +1,47 @@
import 'package:hiddingsel_app/appflow/model/schedules.dart';
import 'package:hiddingsel_app/services/storage.dart';
import 'contacts.dart';
import 'event_organisations.dart';
import 'interfaces.dart';
class EventModel with Name, Contact, JsonEncodable {
final String id;
final ScheduleModel schedule;
final String name;
final ContactModel contact;
final String? description;
List<EventOrganisationModel> eventOrganizer;
EventModel(this.id, this.schedule, this.name, this.contact,
{this.description, List<EventOrganisationModel>? eventOrganizer})
: this.eventOrganizer = eventOrganizer ?? [];
factory EventModel.fromJson(Map<String, dynamic> json) => EventModel(
json['id'] as String,
ScheduleModel.fromJson(json['schedule'] as Map<String, dynamic>),
json['name'] as String,
ContactModel.fromJson(json['contact'] as Map<String, dynamic>),
description: json['description'] as String?,
eventOrganizer: (json['eventOrganizer'] as List<dynamic>).map((id) => EventOrganisationModel.fromId(id)).cast<EventOrganisationModel>().toList(),
);
Map<String, dynamic> toJson() => {
'id': id,
'schedule': schedule.toJson(),
'name': name,
'contact': contact.toJson(),
'description': description,
'eventOrganizer': eventOrganizer.map((e) => e.id).toList(),
};
@override
bool operator ==(Object other) =>
other is EventModel &&
runtimeType == other.runtimeType &&
(id == other.id || (schedule.startTime == other.schedule.startTime && name == other.name));
@override
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,47 @@
import 'package:get_it/get_it.dart';
import 'package:hiddingsel_app/appflow/model/represented_organisations.dart';
import 'package:hiddingsel_app/appflow/model/topics.dart';
import '../controller/settings.dart';
import 'interfaces.dart';
class EventOrganisationModel extends PushNotificationTopicModel with AllowsEventNotifications {
final Uri? ical;
Future<bool> get eventNotificationAllowed => SettingsController.eventNotificationAllowed(this);
allowEventNotification(bool allowed) {
if (allowed) {
SettingsController.allowEventNotification(this);
} else {
SettingsController.unallowEventNotification(this);
}
}
const EventOrganisationModel(id, name, this.ical) : super(id, name);
factory EventOrganisationModel.fromJson(Map<String, dynamic> json) {
return EventOrganisationModel(
json['id'] as String,
json['name'] as String,
(json['ical'] as String?) != null
? Uri.parse(json['ical'] as String)
: null,
);
}
static EventOrganisationModel? fromId(String id) =>
EventOrganisationModel.values.cast<EventOrganisationModel?>().firstWhere((topic) => topic?.id == id, orElse: () => null);
static List<EventOrganisationModel> values = GetIt.I<List<EventOrganisationModel>>()
..addAll(RepresentedOrganisationModel.values)
..sort((a, b) => a.name.compareTo(b.name));
bool operator == (Object other) =>
other is EventOrganisationModel &&
runtimeType == other.runtimeType &&
id == other.id;
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,18 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart';
class ImageModel {
final String resource;
ImageProvider get image {
if (resource.startsWith('http')) {
return CachedNetworkImageProvider(resource);
} else if (resource.startsWith('assets')) {
return AssetImage(resource);
} else {
throw Exception('Invalid image resource: $resource');
}
}
const ImageModel(this.resource);
}

View File

@@ -0,0 +1,42 @@
import 'package:hiddingsel_app/appflow/model/persons.dart';
import 'images.dart';
import 'contacts.dart';
mixin Name {
String get name;
}
mixin Contact {
ContactModel get contact;
}
mixin Person {
PersonModel get person;
}
mixin Image {
ImageModel get image;
}
mixin IsShareable {
Uri get url;
}
mixin IsSearchable {
get tags;
}
mixin IsFavorable {
get favorized;
favorize(bool favorize);
}
mixin IsSubscribable {
get subscribed;
subscribe(bool subscribe);
}
mixin AllowsEventNotifications {
get eventNotificationAllowed;
allowEventNotification(bool allowed);
}

View File

@@ -0,0 +1,20 @@
class LocationModel {
final double? latitude;
final double? longitude;
final String? address;
LocationModel({this.latitude, this.longitude, this.address});
factory LocationModel.fromJson(Map<String, dynamic> json) => LocationModel(
latitude: json["latitude"] as double?,
longitude: json["longitude"] as double?,
address: json["address"] as String?,
);
Map<String, dynamic> toJson() => {
"latitude": latitude,
"longitude": longitude,
"address": address,
};
}

View File

@@ -0,0 +1,23 @@
import 'package:hiddingsel_app/appflow/model/images.dart';
import 'package:hiddingsel_app/appflow/model/interfaces.dart';
class PersonModel with Name, IsSearchable{
final String name;
final ImageModel image;
final String? position;
get tags => [name];
const PersonModel.withImageModel(this.name, this.image, {this.position});
PersonModel(this.name, String imageResource, {this.position}) : image = ImageModel(imageResource);
factory PersonModel.fromJson(Map<String, dynamic> json) => PersonModel(
json['name'] as String,
json['image'] as String,
position: json['position'] as String?,
);
}

View File

@@ -0,0 +1,31 @@
import 'package:get_it/get_it.dart';
import 'package:hiddingsel_app/appflow/model/images.dart';
import 'interfaces.dart';
class PointOfInterestModel with Name {
final String name;
final String? subtitle;
final String text;
final ImageModel image;
final List<ImageModel> otherImages;
const PointOfInterestModel.withImageModel(this.name, this.subtitle, this.text, this.image, this.otherImages);
PointOfInterestModel(this.name, this.subtitle, this.text, String imageResource, List<String> otherImagesResources) : image = ImageModel(imageResource), otherImages = otherImagesResources.map((e) => ImageModel(e)).toList();
factory PointOfInterestModel.fromJson(Map<String, dynamic> json) {
return PointOfInterestModel(
json['name'] as String,
json['subtitle'] as String?,
json['text'] as String,
json['assetThumbnailImage'] as String,
(json['assetsOtherImages'] as List<dynamic>).cast<String>(),
);
}
static final List<PointOfInterestModel> values = GetIt.I<List<PointOfInterestModel>>();
}

View File

@@ -0,0 +1,79 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:get_it/get_it.dart';
import 'package:hiddingsel_app/appflow/model/images.dart';
import 'package:hiddingsel_app/appflow/model/persons.dart';
import 'package:hiddingsel_app/appflow/model/event_organisations.dart';
import '../../constants/constant.dart';
import '../../services/environment.dart';
import '../controller/settings.dart';
import 'contacts.dart';
import 'interfaces.dart';
class RepresentedOrganisationModel extends EventOrganisationModel
with Contact, Person, IsFavorable, IsSearchable {
final int wordpressId;
final ImageModel image;
final PersonModel person;
final ContactModel contact;
InlineSpan get text => stringToInlineSpan(info);
final String? info;
final List<String> _tags;
List<String> get tags => [name]..addAll(_tags)..addAll(person.tags)..addAll(contact.tags);
Future<bool> get favorized => SettingsController.favorized(this);
favorize(bool favorize) {
if (favorize) {
SettingsController.favorize(this);
} else {
SettingsController.unfavorize(this);
}
}
InlineSpan stringToInlineSpan(String? text) {
final textblocks = text?.split('\\');
return TextSpan(children: textblocks?.map((textblock)
{
if (textblock.startsWith('link=')) {
String link = textblock.substring(5);
return TextSpan(text: link,
style: UITheme.theme.textTheme.bodyLarge,
recognizer: TapGestureRecognizer()..onTap = () => EnvironmentConnector.openBrowser(Uri.parse(link)));
} else {
return TextSpan(text: textblock,
style: UITheme.theme.textTheme.bodyLarge);
}
}).toList());
}
const RepresentedOrganisationModel.withImageModel(id, name, this.wordpressId, this.image, this.person, this.contact, this.info, this._tags, Uri? ical) : super(id, name, ical);
RepresentedOrganisationModel(id, name, this.wordpressId, String imageResource, this.person, this.contact, this.info, this._tags, Uri? ical) : image = ImageModel(imageResource), super(id, name, ical);
factory RepresentedOrganisationModel.fromJson(Map<String, dynamic> json) {
return RepresentedOrganisationModel(
json['id'] as String,
json['name'] as String,
json['wordpress_id'] as int,
json['image'] as String,
PersonModel.fromJson(json['person'] as Map<String, dynamic>),
ContactModel.fromJson(json['contact'] as Map<String, dynamic>),
json['text'] as String?,
(json['tags'] as List<dynamic>).cast<String>(),
(json['ical'] as String?) != null
? Uri.parse(json['ical'] as String)
: null,
);
}
static RepresentedOrganisationModel? fromWordpressId(int wordpressId) =>
RepresentedOrganisationModel.values.cast<RepresentedOrganisationModel?>().firstWhere((o) => o?.wordpressId == wordpressId, orElse: () => null);
static List<RepresentedOrganisationModel> values = GetIt.I<List<RepresentedOrganisationModel>>()..sort((a, b) => a.name.compareTo(b.name));
bool operator ==(Object other) =>
other is RepresentedOrganisationModel &&
runtimeType == other.runtimeType &&
id == other.id;
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,32 @@
import 'package:hiddingsel_app/constants/enums.dart';
class ScheduleModel {
final DateTime startTime;
final DateTime endTime;
final Frequency frequency;
final int? interval;
final DateTime? until;
ScheduleModel(
this.startTime, this.endTime, this.frequency, this.interval, this.until);
bool get isAllDay =>
startTime == endTime || (startTime.difference(endTime).inDays != 0 && startTime.difference(endTime).inDays%1 == 0);
toJson() => {
'startTime': startTime.toIso8601String(),
'endTime': endTime.toIso8601String(),
'frequency': frequency.toString(),
'interval': interval,
'until': until?.toIso8601String(),
};
static ScheduleModel fromJson(Map<String, dynamic> json) => ScheduleModel(
DateTime.parse(json['startTime'] as String),
DateTime.parse(json['endTime'] as String),
Frequency.values
.firstWhere((f) => f.toString() == json['frequency']),
json['interval'] as int?,
(json['until'] as String?) != null ? DateTime.parse(json['until'] as String) : null,
);
}

View File

@@ -0,0 +1,41 @@
import 'package:get_it/get_it.dart';
import '../controller/settings.dart';
import '../../services/notification.dart';
import 'companies.dart';
import 'event_organisations.dart';
import 'interfaces.dart';
class PushNotificationTopicModel with Name, IsSubscribable {
final String id;
final String name;
get subscribed => SettingsController.subscribed(this);
subscribe(bool subscribe) {
if(subscribe) {
SettingsController.subscribeTopic(this);
ExternalNotificationConnector.subscribe(this);
} else {
SettingsController.unsubscribeTopic(this);
ExternalNotificationConnector.unsubscribe(this);
}
}
const PushNotificationTopicModel(this.id, this.name);
factory PushNotificationTopicModel.fromJson(Map<String, dynamic> json) {
return PushNotificationTopicModel(
json['id'] as String,
json['name'] as String
);
}
static List<PushNotificationTopicModel> values = GetIt.I<List<PushNotificationTopicModel>>()..addAll(EventOrganisationModel.values)..addAll(CompanyModel.values)..sort((a, b) => a.name.compareTo(b.name));
bool operator ==(Object other) =>
other is PushNotificationTopicModel &&
runtimeType == other.runtimeType &&
id == other.id;
int get hashCode => id.hashCode;
}