-
Notifications
You must be signed in to change notification settings - Fork 4
feat: Add App Message feature for displaying static backend message #530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 2 commits
58767f8
ee46abc
cf90571
5098254
ab4f8a9
832c3d8
9e9378c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| class AppMessageModel { | ||
| final String? id; | ||
| final String title; | ||
| final String message; | ||
| final bool blocking; | ||
| final String platform; // e.g. "all", "ios", "android" | ||
| final DateTime? startDate; | ||
| final DateTime? endDate; | ||
|
|
||
| AppMessageModel({ | ||
| required this.id, | ||
| required this.title, | ||
| required this.message, | ||
| required this.blocking, | ||
| required this.platform, | ||
| this.startDate, | ||
| this.endDate, | ||
| }); | ||
|
|
||
| factory AppMessageModel.fromJson(Map<String, dynamic> json) { | ||
| return AppMessageModel( | ||
| id: json['id'], | ||
| title: json['title'] ?? '', | ||
| message: json['message'] ?? '', | ||
| blocking: json['blocking'] ?? false, | ||
| platform: json['platform'] ?? 'all', | ||
| startDate: _parseDate(json['start_date']), | ||
| endDate: _parseDate(json['end_date']), | ||
| ); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ahmed-tarek-salem how localised message would be handled?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| /// Check if the message is currently active | ||
| bool get isActive { | ||
| final now = DateTime.now().toUtc(); | ||
| if (startDate != null && now.isBefore(startDate!)) return false; | ||
| if (endDate != null && now.isAfter(endDate!)) return false; | ||
| return true; | ||
| } | ||
|
|
||
| /// Check if message applies to current platform | ||
| bool appliesToPlatform(String currentPlatform) { | ||
| final lowerPlatform = platform.toLowerCase(); | ||
| return lowerPlatform == 'all' || | ||
| lowerPlatform == currentPlatform.toLowerCase(); | ||
| } | ||
|
|
||
| bool isValid(String currentPlatform) { | ||
| return isActive && appliesToPlatform(currentPlatform); | ||
| } | ||
|
|
||
| static DateTime? _parseDate(dynamic value) { | ||
| if (value == null) return null; | ||
| try { | ||
| return DateTime.parse(value).toUtc(); | ||
| } catch (_) { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter_mobx/flutter_mobx.dart'; | ||
| import 'package:flutter_svg/flutter_svg.dart'; | ||
| import 'package:qubic_wallet/di.dart'; | ||
| import 'package:qubic_wallet/dtos/app_message_dto.dart'; | ||
| import 'package:qubic_wallet/flutter_flow/theme_paddings.dart'; | ||
| import 'package:qubic_wallet/stores/app_message_store.dart'; | ||
| import 'package:qubic_wallet/styles/app_icons.dart'; | ||
| import 'package:qubic_wallet/styles/text_styles.dart'; | ||
| import 'package:qubic_wallet/styles/themed_controls.dart'; | ||
|
|
||
| class MaintenanceScreen extends StatelessWidget { | ||
| final AppMessageModel? appMessage; | ||
| const MaintenanceScreen({super.key, this.appMessage}); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return PopScope( | ||
| canPop: false, | ||
| child: Scaffold( | ||
| body: Center( | ||
| child: Padding( | ||
| padding: const EdgeInsets.symmetric( | ||
| horizontal: ThemePaddings.normalPadding), | ||
| child: Column( | ||
| mainAxisAlignment: MainAxisAlignment.center, | ||
| children: [ | ||
| SvgPicture.asset( | ||
| AppIcons.maintenance, | ||
| height: 100, | ||
| colorFilter: const ColorFilter.mode( | ||
| LightThemeColors.primary40, BlendMode.srcIn), | ||
| ), | ||
| ThemedControls.spacerVerticalNormal(), | ||
| Text(appMessage?.title ?? "", | ||
| style: TextStyles.alertHeader, textAlign: TextAlign.center), | ||
| ThemedControls.spacerVerticalSmall(), | ||
| Text(appMessage?.message ?? "", | ||
| style: TextStyles.alertText, textAlign: TextAlign.center), | ||
| ThemedControls.spacerVerticalNormal(), | ||
| Observer(builder: (context) { | ||
| bool isLoading = getIt<AppMessageStore>().isLoading; | ||
| return ThemedControls.primaryButtonSmall( | ||
| onPressed: () async { | ||
| final navigator = Navigator.of(context); | ||
| final message = | ||
| await getIt<AppMessageStore>().getAppMessage(); | ||
| if (message == null) { | ||
| navigator.pop(); | ||
| } | ||
| }, | ||
| text: isLoading ? "" : "Refresh", | ||
|
ahmed-tarek-salem marked this conversation as resolved.
Outdated
|
||
| icon: isLoading | ||
| ? const SizedBox( | ||
| height: 20, | ||
| width: 20, | ||
| child: CircularProgressIndicator( | ||
| strokeWidth: 2, | ||
| color: LightThemeColors.background), | ||
| ) | ||
| : null); | ||
| }), | ||
| ], | ||
| ), | ||
| )), | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import 'dart:convert'; | ||
|
|
||
| import 'package:dio/dio.dart'; | ||
| import 'package:qubic_wallet/config.dart'; | ||
| import 'package:qubic_wallet/dtos/app_message_dto.dart'; | ||
| import 'package:qubic_wallet/models/app_error.dart'; | ||
| import 'package:qubic_wallet/services/dio_client.dart'; | ||
|
|
||
| class QubicStaticApi { | ||
| late Dio _dio; | ||
|
|
||
| QubicStaticApi() { | ||
| _dio = DioClient.getDio(baseUrl: Config.qubicStaticApiUrl); | ||
| } | ||
|
|
||
| Future<AppMessageModel?> getAppMessage() async { | ||
| try { | ||
| final response = await _dio.get(Config.qubicStaticMessages); | ||
| final decodedResponse = json.decode(response.data); | ||
| return decodedResponse["id"] == null | ||
| ? null | ||
| : AppMessageModel.fromJson(decodedResponse); | ||
| } catch (error) { | ||
| throw ErrorHandler.handleError(error); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import 'package:mobx/mobx.dart'; | ||
| import 'package:qubic_wallet/di.dart'; | ||
| import 'package:qubic_wallet/dtos/app_message_dto.dart'; | ||
| import 'package:qubic_wallet/helpers/app_logger.dart'; | ||
| import 'package:qubic_wallet/resources/apis/static/qubic_static_api.dart'; | ||
| import 'package:universal_platform/universal_platform.dart'; | ||
|
|
||
| part 'app_message_store.g.dart'; | ||
|
|
||
| // ignore: library_private_types_in_public_api | ||
| class AppMessageStore = _AppMessageStore with _$AppMessageStore; | ||
|
|
||
| abstract class _AppMessageStore with Store { | ||
| @observable | ||
| bool isLoading = false; | ||
|
|
||
| @action | ||
| Future<AppMessageModel?> getAppMessage() async { | ||
| isLoading = true; | ||
| try { | ||
| final message = await getIt<QubicStaticApi>().getAppMessage(); | ||
| if (message != null && | ||
| message.isValid(UniversalPlatform.operatingSystem)) { | ||
| return message; | ||
| } | ||
| return null; | ||
| } catch (e) { | ||
| appLogger.e(e); | ||
| return null; | ||
| } finally { | ||
| isLoading = false; | ||
| } | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ahmed-tarek-salem can we use startDate and endDate instead?
I think we should prefer this format as it could be mapped automatically to object properties.