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,83 @@
import 'package:hiddingsel_app/appflow/controller/network/wordpress.dart';
import 'package:hiddingsel_app/appflow/model/articles.dart';
import 'package:hiddingsel_app/appflow/model/represented_organisations.dart';
import 'package:hiddingsel_app/constants/constant.dart';
import '../../services/storage.dart';
class ArticleController {
static int defaultSortCompare(ArticleModel a, ArticleModel b) => b.publishedAt.compareTo(a.publishedAt);
static Stream<List<ArticleModel>> getArticleListStream({int Function(ArticleModel, ArticleModel) sortCompare = defaultSortCompare}) async*{
List<ArticleModel> yieldArticles = [];
List<ArticleModel> allWordpressArticles = [];
List<ArticleModel> savedArticles = await DataConnector.getJsonEncodableList<ArticleModel>(SystemStrings.dataArticles, [], ArticleModel.fromJson);
yield yieldArticles..addAll(savedArticles)..sort(sortCompare);
for (var page = 0, perPage = 10; page < 10; page++) {
var wordpressArticles = await WordpressController.getArticles(page, perPage);
var newWordpressArticles = wordpressArticles.where((a) => !yieldArticles.contains(a)).toList();
if (newWordpressArticles.isNotEmpty) {
yield yieldArticles..addAll(newWordpressArticles)..sort(sortCompare);
}
allWordpressArticles = allWordpressArticles..addAll(wordpressArticles)..sort(sortCompare);
DataConnector.saveJsonEncodable(SystemStrings.dataArticles, allWordpressArticles);
}
}
static Stream<List<ArticleModel>> getSearchResultListStream(String searchWord, {int Function(ArticleModel, ArticleModel) sortCompare = defaultSortCompare}) async*{
List<ArticleModel> yieldArticles = [];
for (var page = 0, perPage = 10; page < 3; page++) {
var wordpressArticles = await WordpressController.searchArticles(page, perPage, searchWord);
var newWordpressArticles = wordpressArticles.where((a) => !yieldArticles.contains(a)).toList();
if (newWordpressArticles.isNotEmpty) {
yield yieldArticles..addAll(newWordpressArticles)..sort(sortCompare);
}
}
}
static Stream<List<ArticleModel>> getOrganisationArticleListStream(RepresentedOrganisationModel organisation, {int Function(ArticleModel, ArticleModel) sortCompare = defaultSortCompare}) async*{
List<ArticleModel> yieldArticles = [];
List<ArticleModel> allWordpressArticles = [];
List<ArticleModel> savedArticles = await DataConnector.getJsonEncodableList<ArticleModel>(SystemStrings.dataArticles + organisation.id, [], ArticleModel.fromJson);
yield yieldArticles..addAll(savedArticles)..sort(sortCompare);
for (var page = 0, perPage = 10; page < 10; page++) {
var wordpressArticles = await WordpressController.getArticlesFromOrganisations(page, perPage, [organisation]);
var newWordpressArticles = wordpressArticles.where((a) => !yieldArticles.contains(a)).toList();
if (newWordpressArticles.isNotEmpty) {
yield yieldArticles..addAll(newWordpressArticles)..sort(sortCompare);
}
allWordpressArticles = allWordpressArticles..addAll(wordpressArticles)..sort(sortCompare);
DataConnector.saveJsonEncodable(SystemStrings.dataArticles + organisation.id, allWordpressArticles);
}
}
static Stream<List<ArticleModel>> getFavoriteArticleListStream({int Function(ArticleModel, ArticleModel) sortCompare = defaultSortCompare}) async*{
var favoriteOrganisations = List<RepresentedOrganisationModel>.empty(growable: true);
for (RepresentedOrganisationModel organisation in RepresentedOrganisationModel.values) {
if(await organisation.favorized) favoriteOrganisations.add(organisation);
}
List<ArticleModel> yieldArticles = [];
List<ArticleModel> allWordpressArticles = [];
List<ArticleModel> savedArticles = await DataConnector.getJsonEncodableList<ArticleModel>(SystemStrings.dataFavoriteArticles, [], ArticleModel.fromJson);
List<ArticleModel> savedFavoriteArticles = savedArticles.where((e) => e.categories.any((orga) => favoriteOrganisations.contains(orga))).toList();
yield yieldArticles..addAll(savedFavoriteArticles)..sort(sortCompare);
for (var page = 0, perPage = 10; page < 10; page++) {
var wordpressArticles = await WordpressController.getArticlesFromOrganisations(page, perPage, favoriteOrganisations);
var newWordpressArticles = wordpressArticles.where((a) => !yieldArticles.contains(a)).toList();
if (newWordpressArticles.isNotEmpty) {
yield yieldArticles..addAll(newWordpressArticles)..sort(sortCompare);
}
allWordpressArticles = allWordpressArticles..addAll(wordpressArticles)..sort(sortCompare);
DataConnector.saveJsonEncodable(SystemStrings.dataFavoriteArticles, allWordpressArticles);
}
}
}

View File

@@ -0,0 +1,68 @@
import 'package:hiddingsel_app/appflow/controller/network/calendar.dart';
import 'package:hiddingsel_app/appflow/model/event.dart';
import 'package:hiddingsel_app/appflow/model/event_organisations.dart';
import '../../constants/constant.dart';
import '../../services/storage.dart';
import '../../todo/scheduled_notification.dart';
class EventController {
static int defaultSortCompare(EventModel a, EventModel b) => a.schedule.startTime.compareTo(b.schedule.startTime);
static Stream<List<EventModel>> getEventListStream({int Function(EventModel, EventModel) sortCompare = defaultSortCompare}) async* {
List<EventModel> yieldEvents = [];
List<EventModel> allEvents = [];
List<EventModel> newEvents = [];
List<EventModel> organisationEvents = [];
List<EventModel> savedEvents = await DataConnector.getJsonEncodableList<
EventModel>(SystemStrings.dataEvents, [], EventModel.fromJson);
yield yieldEvents = yieldEvents..addAll(savedEvents)..sort(sortCompare);
for (EventOrganisationModel eventOrganisation in EventOrganisationModel.values){
organisationEvents = await CalendarController
.getOrganisationEvents(eventOrganisation);
newEvents = organisationEvents.where((a) => !yieldEvents.contains(a)).toList();
if (newEvents.isNotEmpty) {
yield yieldEvents
..addAll(newEvents)
..sort(sortCompare);
}
allEvents = allEvents..addAll(organisationEvents)..sort(sortCompare);
}
DataConnector.saveJsonEncodable(SystemStrings.dataEvents, allEvents);
}
static Future<void> scheduleNotificationsBySettings() async {
List<EventOrganisationModel> notifiableOrganisations = await _getNotifiableOrganisations();
final duration = Duration(milliseconds: await SettingsConnector.getInt(SystemStrings.keyAlertTime, 1*60*60*1000));
await ScheduledNotificationConnector.cancelAllScheduledNotification();
EventController.getEventListStream().forEach((List<EventModel> list) async{
var eventsToSchedule = list
.where((e) => e.schedule.startTime.isAfter(DateTime.now()))
.where((e) => notifiableOrganisations.any((o) => e.eventOrganizer.contains(o)))
.toList();
_scheduleNotifications(eventsToSchedule, duration);
});
}
static _getNotifiableOrganisations() async {
var notifiableOrganisations = List<EventOrganisationModel>.empty(growable: true);
for (EventOrganisationModel event in EventOrganisationModel.values) {
if(await event.eventNotificationAllowed) notifiableOrganisations.add(event);
}
return notifiableOrganisations;
}
static _scheduleNotifications(List<EventModel> eventsToSchedule,
Duration duration) {
eventsToSchedule.sort((a, b) => a.schedule.startTime.compareTo(b.schedule.startTime));
Future.forEach(eventsToSchedule.take(50), (EventModel element) async {
ScheduledNotificationConnector.scheduleNotification(
element.id.hashCode,
element.schedule.startTime.subtract(duration),
element.name,
'${element.eventOrganizer.first.name} - Der Termin beginnt bald.');
});
}
}

View File

@@ -0,0 +1,65 @@
import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
import 'package:hiddingsel_app/appflow/controller/events.dart';
import 'package:hiddingsel_app/appflow/model/pois.dart';
import 'package:hiddingsel_app/appflow/model/represented_organisations.dart';
import 'package:hiddingsel_app/constants/constant.dart';
import 'package:hiddingsel_app/appflow/model/event_organisations.dart';
import 'package:hiddingsel_app/services/storage.dart';
import 'package:hiddingsel_app/todo/scheduled_notification.dart';
import '../model/companies.dart';
import '../model/topics.dart';
import '../../services/notification.dart';
import 'package:timezone/data/latest_all.dart' as tz;
class InitialStartController {
static Future<void> ensureInitialized() async {
await initializeAlways();
SettingsConnector.getBool(SystemStrings.keyFirstTime, true).then((firstTime)
{if(firstTime) {
initializeFirstTime();
SettingsConnector.setBool(SystemStrings.keyFirstTime, false);
}});
}
static Future<void> initializeAlways() async {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: UIColors.white,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
));
//RemoteConfigConnector.initialize();
ExternalNotificationConnector.initialize();
await ScheduledNotificationConnector.initialize();
tz.initializeTimeZones();
await load<PointOfInterestModel>(SystemStrings.assetPois, PointOfInterestModel.fromJson);
await load<CompanyModel>(SystemStrings.assetCompanies, CompanyModel.fromJson);
await load<RepresentedOrganisationModel>(SystemStrings.assetRepresentedOrganisations, RepresentedOrganisationModel.fromJson);
await load<EventOrganisationModel>(SystemStrings.assetEventOrganisations, EventOrganisationModel.fromJson);
await load<PushNotificationTopicModel>(SystemStrings.assetTopics, PushNotificationTopicModel.fromJson);
EventController.scheduleNotificationsBySettings();
}
static Future<void> load<T>(asset, fromJson) async {
final jsonData = await AssetConnector.getJson(asset);
List<T> list = jsonData.map((json) => fromJson(json)).toList().cast<T>();
GetIt.I.registerSingleton<List<T>>(list);
}
static void initializeFirstTime() async {
PushNotificationTopicModel.values.forEach((e) => e.subscribe(false));
CompanyModel.values.forEach((e) => e.subscribe(false));
RepresentedOrganisationModel.values.forEach((e) => e.favorize(false));
EventOrganisationModel.values.forEach((e) => e.allowEventNotification(false));
EventOrganisationModel.values.where((e) => e.id == "garbage").forEach((e) => e.allowEventNotification(true));
EventOrganisationModel.values.where((e) => e.id == "dorfgemeinschaft").forEach((e) => e.allowEventNotification(true));
EventController.scheduleNotificationsBySettings();
}
}

View File

@@ -0,0 +1,21 @@
import 'package:hiddingsel_app/appflow/model/event_organisations.dart';
import '../../../services/network.dart';
import '../../../todo/parser.dart';
import '../../model/event.dart';
import 'package:http/http.dart';
import 'dart:convert' show utf8;
class CalendarController {
static Future<List<EventModel>> getOrganisationEvents(EventOrganisationModel eventOrganisation) {
if(eventOrganisation.ical == null) return Future.value(List<EventModel>.empty());
return BaseService.getSaveFromServer(
() => _getEvents(eventOrganisation.ical), List<EventModel>.empty());
}
static Future<List<EventModel>> _getEvents(icalUri) async {
final response = await get(icalUri);
return IcalParser.parse(utf8.decode(response.bodyBytes));
}
}

View File

@@ -0,0 +1,64 @@
import 'dart:convert';
import 'package:hiddingsel_app/appflow/model/represented_organisations.dart';
import '../../../services/network.dart';
import '../../model/articles.dart';
import 'package:http/http.dart';
class WordpressController {
static const String _baseUrl = 'www.hiddingsel.de';
static const String _path = 'wp-json/wp/v2/posts';
static const String _parameterPage = 'page';
static const String _parameterPerPage = 'per_page';
static const String _parameterSearch = 'search';
static const String _parameterCategories = 'categories';
static const String _parameterEmbed = '_embed';
static const int _httpCodeKeineWeiterenArticel = 400;
static Future<List<ArticleModel>> getArticles(int page, int itemCount) =>
BaseService.getSaveFromServer(
() => _getArticles(page, itemCount),
List<ArticleModel>.empty()
);
static Future<List<ArticleModel>> searchArticles(int page, int itemCount, String searchWord) =>
BaseService.getSaveFromServer(
() => _getArticles(page, itemCount, searchWord: searchWord),
List<ArticleModel>.empty()
);
static Future<List<ArticleModel>> getArticlesFromOrganisations(int page, int itemCount, List<RepresentedOrganisationModel> organisations) =>
BaseService.getSaveFromServer(
() => _getArticles(page, itemCount, organisations: organisations),
List<ArticleModel>.empty()
);
static Future<List<ArticleModel>> _getArticles(int page, int itemCount, {String? searchWord, List<RepresentedOrganisationModel>? organisations}) async {
final parameters = <String, dynamic> {
_parameterPage: (page + 1).toString(),
_parameterPerPage: itemCount.toString(),
_parameterSearch: searchWord,
_parameterCategories: organisations?.map((e) => e.wordpressId).join(','),
_parameterEmbed: null,
};
if(searchWord?.isEmpty ?? true) {
parameters.remove(_parameterSearch);
}
if(organisations?.isEmpty ?? true) {
parameters.remove(_parameterCategories);
}
final uri = Uri.https(_baseUrl, _path, parameters);
final response = await get(uri);
if (response.statusCode == _httpCodeKeineWeiterenArticel) {
return [];
}
final jsonData = json.decode(response.body);
return jsonData.map((article) => ArticleModel.fromWordpressJson(article)).cast<ArticleModel>().toList();
}
}

View File

@@ -0,0 +1,45 @@
import 'package:hiddingsel_app/appflow/model/event_organisations.dart';
import 'package:hiddingsel_app/appflow/model/topics.dart';
import 'package:hiddingsel_app/services/storage.dart';
import '../../constants/constant.dart';
import 'events.dart';
class SettingsController {
static const subscribtion = SystemStrings.keyPreSubscribtion;
static const eventNotification = SystemStrings.keyPreEventNotification;
static const favorization = SystemStrings.keyPreFavorization;
static void subscribeTopic(PushNotificationTopicModel topic) =>
SettingsConnector.setBool(subscribtion + topic.id, true);
static void unsubscribeTopic(PushNotificationTopicModel topic) =>
SettingsConnector.setBool(subscribtion + topic.id, false);
static Future<bool> subscribed(PushNotificationTopicModel topic) =>
SettingsConnector.getBool(subscribtion + topic.id, false);
static void allowEventNotification(EventOrganisationModel eventOrganisation) async {
await SettingsConnector.setBool(eventNotification + eventOrganisation.id, true);
EventController.scheduleNotificationsBySettings();
}
static void unallowEventNotification(EventOrganisationModel eventOrganisation) async {
await SettingsConnector.setBool(eventNotification + eventOrganisation.id, false);
EventController.scheduleNotificationsBySettings();
}
static Future<bool> eventNotificationAllowed(EventOrganisationModel eventOrganisation) =>
SettingsConnector.getBool(eventNotification + eventOrganisation.id, false);
static void favorize(PushNotificationTopicModel topic) =>
SettingsConnector.setBool(favorization + topic.id, true);
static void unfavorize(PushNotificationTopicModel topic) =>
SettingsConnector.setBool(favorization + topic.id, false);
static Future<bool> favorized(PushNotificationTopicModel topic) =>
SettingsConnector.getBool(favorization + topic.id, false);
}