feat: add news ticker widget for scrolling headlines
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 3m25s

- Add NewsTickerWidget with horizontal auto-scrolling at 40px/sec
- Add NewsData and NewsHeadline Freezed models
- Add news datasource fetching from /tools/news endpoint
- Add news provider with 30-minute auto-refresh
- Place ticker between Welcome card and System Stats on dashboard
- Show placeholder headlines when no data (italic, muted style)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-01-08 21:51:32 +01:00
co-authored by Claude Opus 4.5
parent a376482cd1
commit 4331555f84
10 changed files with 345 additions and 9 deletions
@@ -0,0 +1,38 @@
import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:tatlock_ui/core/api/api_client.dart';
import 'package:tatlock_ui/features/front_hall/data/models/news_model.dart';
part 'news_datasource.g.dart';
/// Data source for news data operations.
///
/// Fetches news headlines from Core API.
class NewsDatasource {
NewsDatasource(this._dio);
final Dio _dio;
static const _basePath = '/tools/news';
/// Gets current news headlines.
///
/// Returns headlines for the news ticker.
Future<NewsData> getNews() async {
final response = await _dio.get<Map<String, dynamic>>(_basePath);
final data = response.data;
if (data == null) {
throw Exception('Failed to fetch news data');
}
return NewsData.fromJson(data);
}
}
/// Provides the news datasource.
@riverpod
NewsDatasource newsDatasource(Ref ref) {
final dio = ref.watch(coreApiClientProvider);
return NewsDatasource(dio);
}
@@ -0,0 +1,34 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'news_model.freezed.dart';
part 'news_model.g.dart';
/// News response from Core API.
/// Contains headlines for the news ticker.
@freezed
sealed class NewsData with _$NewsData {
const factory NewsData({
required List<NewsHeadline> headlines,
String? category,
List<String>? sources,
@JsonKey(name: 'updated_at') required DateTime updatedAt,
String? user,
}) = _NewsData;
factory NewsData.fromJson(Map<String, dynamic> json) =>
_$NewsDataFromJson(json);
}
/// Single news headline.
@freezed
sealed class NewsHeadline with _$NewsHeadline {
const factory NewsHeadline({
required String title,
String? description,
String? source,
String? url,
}) = _NewsHeadline;
factory NewsHeadline.fromJson(Map<String, dynamic> json) =>
_$NewsHeadlineFromJson(json);
}