Implement settings
This commit is contained in:
parent
5417796f3f
commit
b91ddc172d
2
flutter/.gitignore
vendored
2
flutter/.gitignore
vendored
@ -56,3 +56,5 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
/lib/git_stamp/
|
||||||
|
@ -9,28 +9,28 @@
|
|||||||
|
|
||||||
|
|
||||||
# runs app locally (linux)
|
# runs app locally (linux)
|
||||||
run-linux:
|
run-linux: gen
|
||||||
dart run build_runner build
|
dart run build_runner build
|
||||||
_JAVA_OPTIONS="" flutter run -d linux
|
_JAVA_OPTIONS="" flutter run -d linux
|
||||||
|
|
||||||
# runs app locally (web | not really supported)
|
# runs app locally (web | not really supported)
|
||||||
run-web:
|
run-web: gen
|
||||||
dart run build_runner build
|
dart run build_runner build
|
||||||
_JAVA_OPTIONS="" flutter run -d chrome
|
_JAVA_OPTIONS="" flutter run -d chrome
|
||||||
|
|
||||||
# runs on android device (must have network adb enabled teh correct IP)
|
# runs on android device (must have network adb enabled teh correct IP)
|
||||||
run-android:
|
run-android: gen
|
||||||
ping -c1 10.10.10.177
|
ping -c1 10.10.10.177
|
||||||
adb connect 10.10.10.177:5555
|
adb connect 10.10.10.177:5555
|
||||||
flutter pub run build_runner build
|
flutter pub run build_runner build
|
||||||
_JAVA_OPTIONS="" flutter run -d 10.10.10.177:5555
|
_JAVA_OPTIONS="" flutter run -d 10.10.10.177:5555
|
||||||
|
|
||||||
install-release:
|
install-release: gen
|
||||||
# Install on Pixel 7a
|
# Install on Pixel 7a
|
||||||
flutter build apk --release
|
flutter build apk --release
|
||||||
flutter run --release -d 35221JEHN07157
|
flutter run --release -d 35221JEHN07157
|
||||||
|
|
||||||
build-release:
|
build-release: gen
|
||||||
flutter build apk --release
|
flutter build apk --release
|
||||||
flutter build appbundle --release
|
flutter build appbundle --release
|
||||||
flutter build linux --release
|
flutter build linux --release
|
||||||
@ -42,7 +42,9 @@ fix:
|
|||||||
dart fix --apply
|
dart fix --apply
|
||||||
|
|
||||||
gen:
|
gen:
|
||||||
|
./_utils/inc_buildnum.sh
|
||||||
dart run build_runner build
|
dart run build_runner build
|
||||||
|
dart run git_stamp git_stamp --build-type lite --limit 2
|
||||||
|
|
||||||
# run `make run` in another terminal (or another variant of flutter run)
|
# run `make run` in another terminal (or another variant of flutter run)
|
||||||
autoreload:
|
autoreload:
|
||||||
|
41
flutter/_utils/inc_buildnum.sh
Executable file
41
flutter/_utils/inc_buildnum.sh
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC2002 # disable useless-cat warning
|
||||||
|
|
||||||
|
set -o nounset # disallow usage of unset vars ( set -u )
|
||||||
|
set -o errexit # Exit immediately if a pipeline returns non-zero. ( set -e )
|
||||||
|
set -o errtrace # Allow the above trap be inherited by all functions in the script. ( set -E )
|
||||||
|
set -o pipefail # Return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status
|
||||||
|
IFS=$'\n\t' # Set $IFS to only newline and tab.
|
||||||
|
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
cr=$'\n'
|
||||||
|
|
||||||
|
function black() { if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[30m$1\\x1B[0m"; else echo "$1"; fi }
|
||||||
|
function red() { if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[31m$1\\x1B[0m"; else echo "$1"; fi; }
|
||||||
|
function green() { if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[32m$1\\x1B[0m"; else echo "$1"; fi; }
|
||||||
|
function yellow(){ if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[33m$1\\x1B[0m"; else echo "$1"; fi; }
|
||||||
|
function blue() { if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[34m$1\\x1B[0m"; else echo "$1"; fi; }
|
||||||
|
function purple(){ if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[35m$1\\x1B[0m"; else echo "$1"; fi; }
|
||||||
|
function cyan() { if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[36m$1\\x1B[0m"; else echo "$1"; fi; }
|
||||||
|
function white() { if [ -t 1 ] && [ -n "$(tput colors)" ] && [ "$(tput colors)" -ge 8 ]; then echo -e "\\x1B[37m$1\\x1B[0m"; else echo "$1"; fi; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
path_to_pubspec="$(dirname "$0")/../pubspec.yaml"
|
||||||
|
current_version=$(awk '/^version:/ {print $2}' $path_to_pubspec)
|
||||||
|
current_version_without_build=$(echo "$current_version" | sed 's/\+.*//')
|
||||||
|
|
||||||
|
gitcount="$(git log | grep "^commit" | wc -l | xargs)"
|
||||||
|
new_version="$current_version_without_build+$gitcount"
|
||||||
|
|
||||||
|
echo "Setting pubspec.yaml version $current_version to $new_version"
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# macOS sed (requires a space after -i)
|
||||||
|
sed -i '' -e "s/version: $current_version/version: $new_version/g" $path_to_pubspec
|
||||||
|
else
|
||||||
|
# GNU sed (requires no space after -i)
|
||||||
|
sed -i'' -e "s/version: $current_version/version: $new_version/g" $path_to_pubspec
|
||||||
|
fi
|
@ -5,7 +5,7 @@ import 'package:simplecloudnotifier/components/layout/app_bar_filter_dialog.dart
|
|||||||
import 'package:simplecloudnotifier/components/layout/app_bar_progress_indicator.dart';
|
import 'package:simplecloudnotifier/components/layout/app_bar_progress_indicator.dart';
|
||||||
import 'package:simplecloudnotifier/pages/debug/debug_main.dart';
|
import 'package:simplecloudnotifier/pages/debug/debug_main.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_list/message_filter_chiplet.dart';
|
import 'package:simplecloudnotifier/pages/message_list/message_filter_chiplet.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_events.dart';
|
import 'package:simplecloudnotifier/state/app_events.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_theme.dart';
|
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||||
@ -64,7 +64,7 @@ class _SCNAppBarState extends State<SCNAppBar> {
|
|||||||
builder: (context, appTheme, child) => IconButton(
|
builder: (context, appTheme, child) => IconButton(
|
||||||
icon: Icon(appTheme.darkMode ? FontAwesomeIcons.solidSun : FontAwesomeIcons.solidMoon),
|
icon: Icon(appTheme.darkMode ? FontAwesomeIcons.solidSun : FontAwesomeIcons.solidMoon),
|
||||||
tooltip: appTheme.darkMode ? 'Light mode' : 'Dark mode',
|
tooltip: appTheme.darkMode ? 'Light mode' : 'Dark mode',
|
||||||
onPressed: appTheme.switchDarkMode,
|
onPressed: AppTheme().switchDarkMode,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,7 +14,7 @@ import 'package:simplecloudnotifier/models/scn_message.dart';
|
|||||||
import 'package:simplecloudnotifier/nav_layout.dart';
|
import 'package:simplecloudnotifier/nav_layout.dart';
|
||||||
import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart';
|
import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_events.dart';
|
import 'package:simplecloudnotifier/state/app_events.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_theme.dart';
|
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||||
@ -248,8 +248,10 @@ class SCNApp extends StatelessWidget {
|
|||||||
title: 'SimpleCloudNotifier',
|
title: 'SimpleCloudNotifier',
|
||||||
navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver],
|
navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver],
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
//TODO color settings
|
colorScheme: ColorScheme.fromSeed(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light),
|
seedColor: appTheme.color.value,
|
||||||
|
brightness: appTheme.darkMode ? Brightness.dark : Brightness.light,
|
||||||
|
),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: SCNNavLayout(),
|
home: SCNNavLayout(),
|
||||||
@ -343,7 +345,7 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
|||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.writeRawFailure('Failed to init hive', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground});
|
ApplicationLog.writeRawFailure('Failed to init hive', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground});
|
||||||
ApplicationLog.error('Failed to init hive:' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to init hive:' + exc.toString(), trace: trace);
|
||||||
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (init failed)", null);
|
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (init failed)", null, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,12 +361,13 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
|||||||
final channel = message.data['channel'] as String;
|
final channel = message.data['channel'] as String;
|
||||||
final channel_id = message.data['channel_id'] as String;
|
final channel_id = message.data['channel_id'] as String;
|
||||||
final body = message.data['body'] as String;
|
final body = message.data['body'] as String;
|
||||||
|
final prio = int.parse(message.data['priority'] as String);
|
||||||
|
|
||||||
Notifier.showLocalNotification(scn_msg_id, channel_id, channel, 'Channel: ${channel}', title, body, timestamp);
|
Notifier.showLocalNotification(scn_msg_id, channel_id, channel, 'Channel: ${channel}', title, body, timestamp, prio);
|
||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.writeRawFailure('Failed to decode received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground});
|
ApplicationLog.writeRawFailure('Failed to decode received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground});
|
||||||
ApplicationLog.error('Failed to decode received FB message' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to decode received FB message' + exc.toString(), trace: trace);
|
||||||
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (decode failed)", null);
|
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (decode failed)", null, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +376,7 @@ Future<void> _receiveMessage(RemoteMessage message, bool foreground) async {
|
|||||||
} catch (exc, trace) {
|
} catch (exc, trace) {
|
||||||
ApplicationLog.writeRawFailure('Failed to persist received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground});
|
ApplicationLog.writeRawFailure('Failed to persist received FB message', {'exception': exc.toString(), 'trace': trace.toString(), 'data': message, 'foreground': foreground});
|
||||||
ApplicationLog.error('Failed to persist received FB message' + exc.toString(), trace: trace);
|
ApplicationLog.error('Failed to persist received FB message' + exc.toString(), trace: trace);
|
||||||
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (persist failed)", null);
|
Notifier.showLocalNotification("", "@ERROR", "@ERROR", 'Error Channel', "Error", "Failed to receive SCN message (persist failed)", null, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import 'package:simplecloudnotifier/pages/send/send.dart';
|
|||||||
import 'package:simplecloudnotifier/components/bottom_fab/fab_bottom_app_bar.dart';
|
import 'package:simplecloudnotifier/components/bottom_fab/fab_bottom_app_bar.dart';
|
||||||
import 'package:simplecloudnotifier/pages/account/account.dart';
|
import 'package:simplecloudnotifier/pages/account/account.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_list/message_list.dart';
|
import 'package:simplecloudnotifier/pages/message_list/message_list.dart';
|
||||||
import 'package:simplecloudnotifier/pages/settings/root.dart';
|
import 'package:simplecloudnotifier/pages/settings/settings_view.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
import 'package:simplecloudnotifier/models/channel.dart';
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
@ -9,6 +8,7 @@ import 'package:simplecloudnotifier/models/subscription.dart';
|
|||||||
import 'package:simplecloudnotifier/pages/channel_message_view/channel_message_view.dart';
|
import 'package:simplecloudnotifier/pages/channel_message_view/channel_message_view.dart';
|
||||||
import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart';
|
import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
||||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
@ -20,8 +20,6 @@ enum ChannelListItemMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ChannelListItem extends StatefulWidget {
|
class ChannelListItem extends StatefulWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
const ChannelListItem({
|
const ChannelListItem({
|
||||||
required this.channel,
|
required this.channel,
|
||||||
required this.onChannelListReloadTrigger,
|
required this.onChannelListReloadTrigger,
|
||||||
@ -64,6 +62,8 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||||
@ -95,7 +95,7 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
(widget.channel.timestampLastSent == null) ? '' : ChannelListItem._dateFormat.format(DateTime.parse(widget.channel.timestampLastSent!).toLocal()),
|
(widget.channel.timestampLastSent == null) ? '' : dateFormat.format(DateTime.parse(widget.channel.timestampLastSent!).toLocal()),
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -6,7 +6,7 @@ import 'package:simplecloudnotifier/models/channel.dart';
|
|||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
|
import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/models/client.dart';
|
import 'package:simplecloudnotifier/models/client.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
|
|
||||||
enum ClientListItemMode {
|
enum ClientListItemMode {
|
||||||
Messages,
|
Messages,
|
||||||
@ -9,8 +10,6 @@ enum ClientListItemMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ClientListItem extends StatelessWidget {
|
class ClientListItem extends StatelessWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
const ClientListItem({
|
const ClientListItem({
|
||||||
required this.item,
|
required this.item,
|
||||||
super.key,
|
super.key,
|
||||||
@ -20,6 +19,8 @@ class ClientListItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||||
@ -43,7 +44,7 @@ class ClientListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
ClientListItem._dateFormat.format(DateTime.parse(item.timestampCreated).toLocal()),
|
dateFormat.format(DateTime.parse(item.timestampCreated).toLocal()),
|
||||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -2,6 +2,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/utils/notifier.dart';
|
import 'package:simplecloudnotifier/utils/notifier.dart';
|
||||||
@ -67,8 +68,32 @@ class _DebugActionsPageState extends State<DebugActionsPage> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
UI.button(
|
UI.button(
|
||||||
big: false,
|
big: false,
|
||||||
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null),
|
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, null),
|
||||||
text: 'Show local notification',
|
text: 'Show local notification (generic)',
|
||||||
|
),
|
||||||
|
UI.button(
|
||||||
|
big: false,
|
||||||
|
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 0),
|
||||||
|
text: 'Show local notification (Prio = 0)',
|
||||||
|
),
|
||||||
|
UI.button(
|
||||||
|
big: false,
|
||||||
|
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 1),
|
||||||
|
text: 'Show local notification (Prio = 1)',
|
||||||
|
),
|
||||||
|
UI.button(
|
||||||
|
big: false,
|
||||||
|
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 2),
|
||||||
|
text: 'Show local notification (Prio = 2)',
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
UI.button(
|
||||||
|
big: false,
|
||||||
|
onPressed: () {
|
||||||
|
AppSettings().update((p) => p.reset());
|
||||||
|
Toaster.success("Success", "AppSettings reset to default");
|
||||||
|
},
|
||||||
|
text: 'Reset AppSettings to default',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -11,7 +11,7 @@ class DebugLogsPage extends StatefulWidget {
|
|||||||
class _DebugLogsPageState extends State<DebugLogsPage> {
|
class _DebugLogsPageState extends State<DebugLogsPage> {
|
||||||
Box<SCNLog> logBox = Hive.box<SCNLog>('scn-logs');
|
Box<SCNLog> logBox = Hive.box<SCNLog>('scn-logs');
|
||||||
|
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -6,7 +6,9 @@ class DebugSharedPrefPage extends StatelessWidget {
|
|||||||
final SharedPreferences sharedPref;
|
final SharedPreferences sharedPref;
|
||||||
final List<String> keys;
|
final List<String> keys;
|
||||||
|
|
||||||
DebugSharedPrefPage({required this.sharedPref}) : keys = sharedPref.getKeys().toList();
|
DebugSharedPrefPage({required this.sharedPref}) : keys = sharedPref.getKeys().toList() {
|
||||||
|
keys.sort((a, b) => a.compareTo(b));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -13,7 +13,7 @@ class DebugRequestsPage extends StatefulWidget {
|
|||||||
class _DebugRequestsPageState extends State<DebugRequestsPage> {
|
class _DebugRequestsPageState extends State<DebugRequestsPage> {
|
||||||
Box<SCNRequest> requestsBox = Hive.box<SCNRequest>('scn-requests');
|
Box<SCNRequest> requestsBox = Hive.box<SCNRequest>('scn-requests');
|
||||||
|
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -6,7 +6,7 @@ import 'package:simplecloudnotifier/models/channel.dart';
|
|||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
|
import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
||||||
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_view.dart';
|
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_view.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
|
|
||||||
enum KeyTokenListItemMode {
|
enum KeyTokenListItemMode {
|
||||||
@ -13,8 +15,6 @@ enum KeyTokenListItemMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class KeyTokenListItem extends StatelessWidget {
|
class KeyTokenListItem extends StatelessWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
const KeyTokenListItem({
|
const KeyTokenListItem({
|
||||||
required this.item,
|
required this.item,
|
||||||
super.key,
|
super.key,
|
||||||
@ -24,6 +24,8 @@ class KeyTokenListItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||||
@ -51,7 +53,7 @@ class KeyTokenListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
(item.timestampLastUsed == null) ? '' : KeyTokenListItem._dateFormat.format(DateTime.parse(item.timestampLastUsed!).toLocal()),
|
(item.timestampLastUsed == null) ? '' : dateFormat.format(DateTime.parse(item.timestampLastUsed!).toLocal()),
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@ -14,6 +12,7 @@ import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_channel_modal.d
|
|||||||
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_permission_modal.dart';
|
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_permission_modal.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
import 'package:simplecloudnotifier/state/scn_data_cache.dart';
|
||||||
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||||
@ -45,8 +44,6 @@ enum EditState { none, editing, saving }
|
|||||||
enum KeyTokenViewPageInitState { loading, okay, error }
|
enum KeyTokenViewPageInitState { loading, okay, error }
|
||||||
|
|
||||||
class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
ImmediateFuture<UserPreview> _futureOwner = ImmediateFuture.ofPending();
|
ImmediateFuture<UserPreview> _futureOwner = ImmediateFuture.ofPending();
|
||||||
|
|
||||||
ImmediateFuture<Map<String, ChannelPreview>> _futureAllChannels = ImmediateFuture.ofPending();
|
ImmediateFuture<Map<String, ChannelPreview>> _futureAllChannels = ImmediateFuture.ofPending();
|
||||||
@ -195,6 +192,8 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOwnedKeyTokenView(BuildContext context, KeyToken keytoken) {
|
Widget _buildOwnedKeyTokenView(BuildContext context, KeyToken keytoken) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
||||||
@ -217,13 +216,13 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidClock,
|
icon: FontAwesomeIcons.solidClock,
|
||||||
title: 'Created',
|
title: 'Created',
|
||||||
values: [_KeyTokenViewPageState._dateFormat.format(DateTime.parse(keytoken.timestampCreated).toLocal())],
|
values: [dateFormat.format(DateTime.parse(keytoken.timestampCreated).toLocal())],
|
||||||
),
|
),
|
||||||
UI.metaCard(
|
UI.metaCard(
|
||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.solidClockTwo,
|
icon: FontAwesomeIcons.solidClockTwo,
|
||||||
title: 'Last Used',
|
title: 'Last Used',
|
||||||
values: [(keytoken.timestampLastUsed == null) ? 'Never' : _KeyTokenViewPageState._dateFormat.format(DateTime.parse(keytoken.timestampLastUsed!).toLocal())],
|
values: [(keytoken.timestampLastUsed == null) ? 'Never' : dateFormat.format(DateTime.parse(keytoken.timestampLastUsed!).toLocal())],
|
||||||
),
|
),
|
||||||
_buildOwnerCard(context, true),
|
_buildOwnerCard(context, true),
|
||||||
UI.metaCard(
|
UI.metaCard(
|
||||||
@ -481,8 +480,6 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _editPermissions() async {
|
void _editPermissions() async {
|
||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
||||||
|
|
||||||
if (keytokenUserAccAdmin == null || keytokenUserAccAdmin!.keytokenID == keytokenPreview!.keytokenID) {
|
if (keytokenUserAccAdmin == null || keytokenUserAccAdmin!.keytokenID == keytokenPreview!.keytokenID) {
|
||||||
Toaster.error("Error", "You cannot edit the currently used token");
|
Toaster.error("Error", "You cannot edit the currently used token");
|
||||||
return;
|
return;
|
||||||
@ -502,8 +499,6 @@ class _KeyTokenViewPageState extends State<KeyTokenViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _editChannels() async {
|
void _editChannels() async {
|
||||||
final acc = Provider.of<AppAuth>(context, listen: false);
|
|
||||||
|
|
||||||
if (keytokenUserAccAdmin == null || keytokenUserAccAdmin!.keytokenID == keytokenPreview!.keytokenID) {
|
if (keytokenUserAccAdmin == null || keytokenUserAccAdmin!.keytokenID == keytokenPreview!.keytokenID) {
|
||||||
Toaster.error("Error", "You cannot edit the currently used token");
|
Toaster.error("Error", "You cannot edit the currently used token");
|
||||||
return;
|
return;
|
||||||
|
@ -6,7 +6,7 @@ import 'package:simplecloudnotifier/models/channel.dart';
|
|||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_list/message_filter_chiplet.dart';
|
import 'package:simplecloudnotifier/pages/message_list/message_filter_chiplet.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_events.dart';
|
import 'package:simplecloudnotifier/state/app_events.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
|
@ -2,15 +2,14 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/models/channel.dart';
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
|
|
||||||
class MessageListItem extends StatelessWidget {
|
class MessageListItem extends StatelessWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
static final _lineCount = 3; //TODO setting
|
|
||||||
|
|
||||||
const MessageListItem({
|
const MessageListItem({
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.allChannels,
|
required this.allChannels,
|
||||||
@ -32,6 +31,9 @@ class MessageListItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card buildWithoutChannel(BuildContext context) {
|
Card buildWithoutChannel(BuildContext context) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
final previewLineCount = context.select<AppSettings, int>((v) => v.messagePreviewLength);
|
||||||
|
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||||
@ -57,7 +59,7 @@ class MessageListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_dateFormat.format(DateTime.parse(message.timestamp).toLocal()),
|
dateFormat.format(DateTime.parse(message.timestamp).toLocal()),
|
||||||
style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 11),
|
style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 11),
|
||||||
overflow: TextOverflow.clip,
|
overflow: TextOverflow.clip,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@ -70,10 +72,10 @@ class MessageListItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
processContent(message.content),
|
processContent(message.content, previewLineCount),
|
||||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: _lineCount,
|
maxLines: previewLineCount,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (message.priority == 2) SizedBox(width: 4),
|
if (message.priority == 2) SizedBox(width: 4),
|
||||||
@ -90,6 +92,9 @@ class MessageListItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Card buildWithChannel(BuildContext context) {
|
Card buildWithChannel(BuildContext context) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
final previewLineCount = context.select<AppSettings, int>((v) => v.messagePreviewLength);
|
||||||
|
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||||
@ -113,7 +118,7 @@ class MessageListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Expanded(child: SizedBox()),
|
Expanded(child: SizedBox()),
|
||||||
Text(
|
Text(
|
||||||
_dateFormat.format(DateTime.parse(message.timestamp).toLocal()),
|
dateFormat.format(DateTime.parse(message.timestamp).toLocal()),
|
||||||
style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 11),
|
style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 11),
|
||||||
overflow: TextOverflow.clip,
|
overflow: TextOverflow.clip,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@ -132,10 +137,10 @@ class MessageListItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
processContent(message.content),
|
processContent(message.content, previewLineCount),
|
||||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: _lineCount,
|
maxLines: previewLineCount,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (message.priority == 2) SizedBox(width: 4),
|
if (message.priority == 2) SizedBox(width: 4),
|
||||||
@ -151,7 +156,7 @@ class MessageListItem extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String processContent(String? v) {
|
String processContent(String? v, int lineCount) {
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -161,7 +166,7 @@ class MessageListItem extends StatelessWidget {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.sublist(0, min(_lineCount, lines.length)).join("\n").trim();
|
return lines.sublist(0, min(lineCount, lines.length)).join("\n").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
String processTitle(String? v) {
|
String processTitle(String? v) {
|
||||||
|
@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
|
import 'package:simplecloudnotifier/components/error_display/error_display.dart';
|
||||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||||
import 'package:simplecloudnotifier/models/channel.dart';
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
@ -15,6 +16,7 @@ import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message
|
|||||||
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_view.dart';
|
import 'package:simplecloudnotifier/pages/keytoken_view/keytoken_view.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||||
@ -37,8 +39,6 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
late Future<(SCNMessage, ChannelPreview, KeyTokenPreview, UserPreview)>? mainFuture;
|
late Future<(SCNMessage, ChannelPreview, KeyTokenPreview, UserPreview)>? mainFuture;
|
||||||
(SCNMessage, ChannelPreview, KeyTokenPreview, UserPreview)? mainFutureSnapshot = null;
|
(SCNMessage, ChannelPreview, KeyTokenPreview, UserPreview)? mainFutureSnapshot = null;
|
||||||
|
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
final ScrollController _controller = ScrollController();
|
final ScrollController _controller = ScrollController();
|
||||||
|
|
||||||
bool _monospaceMode = false;
|
bool _monospaceMode = false;
|
||||||
@ -105,7 +105,7 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
final (msg, chn, tok, usr) = snapshot.data!;
|
final (msg, chn, tok, usr) = snapshot.data!;
|
||||||
return _buildMessageView(context, msg, chn, tok, usr);
|
return _buildMessageView(context, msg, chn, tok, usr);
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
return Center(child: Text('${snapshot.error}')); //TODO nice error page
|
return ErrorDisplay(errorMessage: '${snapshot.error}');
|
||||||
} else if (message != null && !this.message!.trimmed) {
|
} else if (message != null && !this.message!.trimmed) {
|
||||||
return _buildMessageView(context, this.message!, null, null, null);
|
return _buildMessageView(context, this.message!, null, null, null);
|
||||||
} else {
|
} else {
|
||||||
@ -247,6 +247,8 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildMessageHeader(BuildContext context, SCNMessage message, ChannelPreview? channel) {
|
List<Widget> _buildMessageHeader(BuildContext context, SCNMessage message, ChannelPreview? channel) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -257,7 +259,7 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
Expanded(child: SizedBox()),
|
Expanded(child: SizedBox()),
|
||||||
Text(_dateFormat.format(DateTime.parse(message.timestamp)), style: const TextStyle(fontSize: 14)),
|
Text(dateFormat.format(DateTime.parse(message.timestamp)), style: const TextStyle(fontSize: 14)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
import 'package:simplecloudnotifier/models/sender_name_statistics.dart';
|
import 'package:simplecloudnotifier/models/sender_name_statistics.dart';
|
||||||
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
import 'package:simplecloudnotifier/pages/filtered_message_view/filtered_message_view.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/utils/navi.dart';
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
|
|
||||||
enum SenderListItemMode {
|
enum SenderListItemMode {
|
||||||
@ -12,8 +13,6 @@ enum SenderListItemMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SenderListItem extends StatelessWidget {
|
class SenderListItem extends StatelessWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
const SenderListItem({
|
const SenderListItem({
|
||||||
required this.item,
|
required this.item,
|
||||||
super.key,
|
super.key,
|
||||||
@ -23,6 +22,8 @@ class SenderListItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return Card.filled(
|
return Card.filled(
|
||||||
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
|
||||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||||
@ -57,7 +58,7 @@ class SenderListItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
SenderListItem._dateFormat.format(DateTime.parse(item.lastTimestamp).toLocal()),
|
dateFormat.format(DateTime.parse(item.lastTimestamp).toLocal()),
|
||||||
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class SettingsRootPage extends StatefulWidget {
|
|
||||||
const SettingsRootPage({super.key, required bool isVisiblePage});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SettingsRootPage> createState() => _SettingsRootPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SettingsRootPageState extends State<SettingsRootPage> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: Text('(coming soon...)'), //TODO
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
117
flutter/lib/pages/settings/settings_number_modal.dart
Normal file
117
flutter/lib/pages/settings/settings_number_modal.dart
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class SettingsNumberModal extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final int currentValue;
|
||||||
|
final int minValue;
|
||||||
|
final int maxValue;
|
||||||
|
final ValueChanged<int> onValueChanged;
|
||||||
|
|
||||||
|
const SettingsNumberModal({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.currentValue,
|
||||||
|
required this.minValue,
|
||||||
|
required this.maxValue,
|
||||||
|
required this.onValueChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingsNumberModal> createState() => _SettingsNumberModalState();
|
||||||
|
|
||||||
|
static Future<void> show(
|
||||||
|
BuildContext context, {
|
||||||
|
required String title,
|
||||||
|
required int currentValue,
|
||||||
|
required int minValue,
|
||||||
|
required int maxValue,
|
||||||
|
required ValueChanged<int> onValueChanged,
|
||||||
|
}) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SettingsNumberModal(
|
||||||
|
title: title,
|
||||||
|
currentValue: currentValue,
|
||||||
|
minValue: minValue,
|
||||||
|
maxValue: maxValue,
|
||||||
|
onValueChanged: onValueChanged,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsNumberModalState extends State<SettingsNumberModal> {
|
||||||
|
late TextEditingController _controller;
|
||||||
|
late int selectedValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
selectedValue = widget.currentValue;
|
||||||
|
_controller = TextEditingController(text: widget.currentValue.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(widget.title),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: _controller,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Enter a number',
|
||||||
|
errorText: _validateInput(),
|
||||||
|
),
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedValue = int.tryParse(value) ?? widget.currentValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _validateInput() == null
|
||||||
|
? () {
|
||||||
|
widget.onValueChanged(selectedValue);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _validateInput() {
|
||||||
|
final number = int.tryParse(_controller.text);
|
||||||
|
if (number == null) {
|
||||||
|
return 'Please enter a valid number';
|
||||||
|
}
|
||||||
|
if (number < widget.minValue) {
|
||||||
|
return 'Value must be at least ${widget.minValue}';
|
||||||
|
}
|
||||||
|
if (number > widget.maxValue) {
|
||||||
|
return 'Value must be at most ${widget.maxValue}';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
43
flutter/lib/pages/settings/settings_picker_screen.dart
Normal file
43
flutter/lib/pages/settings/settings_picker_screen.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
|
|
||||||
|
class SettingsPickerScreen<T> extends StatelessWidget {
|
||||||
|
const SettingsPickerScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.initialValue,
|
||||||
|
required this.values,
|
||||||
|
required this.onValueChanged,
|
||||||
|
this.icons,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final T initialValue;
|
||||||
|
final List<T> values;
|
||||||
|
final void Function(T value) onValueChanged;
|
||||||
|
final Widget Function(T v)? icons;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(title)),
|
||||||
|
body: SettingsList(
|
||||||
|
platform: PlatformUtils.detectPlatform(context),
|
||||||
|
sections: [
|
||||||
|
SettingsSection(
|
||||||
|
tiles: values.map((e) {
|
||||||
|
return SettingsTile(
|
||||||
|
leading: icons != null ? icons!(e) : null,
|
||||||
|
title: Text(e.toString()),
|
||||||
|
onPressed: (_) {
|
||||||
|
onValueChanged(e);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
245
flutter/lib/pages/settings/settings_view.dart
Normal file
245
flutter/lib/pages/settings/settings_view.dart
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
|
import 'package:simplecloudnotifier/git_stamp/git_stamp.dart';
|
||||||
|
import 'package:simplecloudnotifier/pages/settings/settings_number_modal.dart';
|
||||||
|
import 'package:simplecloudnotifier/pages/settings/settings_picker_screen.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/navi.dart';
|
||||||
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
|
||||||
|
class SettingsRootPage extends StatefulWidget {
|
||||||
|
const SettingsRootPage({super.key, required bool isVisiblePage});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingsRootPage> createState() => _SettingsRootPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsRootPageState extends State<SettingsRootPage> {
|
||||||
|
int _multiClickCounter = 0;
|
||||||
|
DateTime? _lastClickTime = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cfg = Provider.of<AppSettings>(context);
|
||||||
|
final thm = Provider.of<AppTheme>(context);
|
||||||
|
|
||||||
|
return SettingsList(
|
||||||
|
platform: PlatformUtils.detectPlatform(context),
|
||||||
|
contentPadding: EdgeInsets.fromLTRB(0, 0, 0, 24),
|
||||||
|
sections: [
|
||||||
|
SettingsSection(
|
||||||
|
title: Text('General'),
|
||||||
|
tiles: [
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(thm.darkMode ? FontAwesomeIcons.solidMoon : FontAwesomeIcons.solidSun),
|
||||||
|
title: Text('Theme'),
|
||||||
|
value: Text(thm.darkMode ? 'Dark' : 'Light'),
|
||||||
|
onPressed: (_) => thm.switchDarkMode(),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidSquare, color: thm.color.value),
|
||||||
|
title: Text('Color'),
|
||||||
|
value: Text(thm.color.displayStr),
|
||||||
|
onPressed: (_) => Navi.push(
|
||||||
|
context,
|
||||||
|
() => SettingsPickerScreen(
|
||||||
|
title: 'Color',
|
||||||
|
initialValue: thm.color,
|
||||||
|
values: ThemeColor.values,
|
||||||
|
icons: (v) => Icon(FontAwesomeIcons.solidSquare, color: v.value),
|
||||||
|
onValueChanged: (value) => AppTheme().setColor(value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidLineColumns),
|
||||||
|
title: Text('Message Preview Lines'),
|
||||||
|
value: Text("${cfg.messagePreviewLength}"),
|
||||||
|
onPressed: (_) {
|
||||||
|
SettingsNumberModal.show(
|
||||||
|
context,
|
||||||
|
title: 'Message Preview Lines',
|
||||||
|
currentValue: cfg.messagePreviewLength,
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 32,
|
||||||
|
onValueChanged: (value) => AppSettings().update((p) => p.messagePreviewLength = value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (Platform.isAndroid)
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: cfg.groupNotifications,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidLayerGroup),
|
||||||
|
title: Text('Group notifications together'),
|
||||||
|
onToggle: (value) => AppSettings().update((p) => p.groupNotifications = !p.groupNotifications),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidCalendarDays),
|
||||||
|
title: Text('Date Format'),
|
||||||
|
value: Text(cfg.dateFormat.displayStr),
|
||||||
|
onPressed: (_) => Navi.push(
|
||||||
|
context,
|
||||||
|
() => SettingsPickerScreen(
|
||||||
|
title: 'Date Format',
|
||||||
|
initialValue: cfg.dateFormat,
|
||||||
|
values: AppSettingsDateFormat.values,
|
||||||
|
onValueChanged: (value) => AppSettings().update((p) => p.dateFormat = value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SettingsSection(
|
||||||
|
title: Text('Priority 0 (Low)'),
|
||||||
|
tiles: _buildNotificationTiles(context, cfg, 0),
|
||||||
|
),
|
||||||
|
SettingsSection(
|
||||||
|
title: Text('Priority 1 (Normal)'),
|
||||||
|
tiles: _buildNotificationTiles(context, cfg, 1),
|
||||||
|
),
|
||||||
|
SettingsSection(
|
||||||
|
title: Text('Priority 2 (High)'),
|
||||||
|
tiles: _buildNotificationTiles(context, cfg, 2),
|
||||||
|
),
|
||||||
|
SettingsSection(
|
||||||
|
title: Text('Advanced Settings'),
|
||||||
|
tiles: [
|
||||||
|
if (cfg.devMode)
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: cfg.showDebugButton,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidSpiderBlackWidow),
|
||||||
|
title: Text('Debug Button anzeigen'),
|
||||||
|
onToggle: (value) => AppSettings().update((p) => p.showDebugButton = !p.showDebugButton),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidList),
|
||||||
|
title: Text('Page Size (Messages)'),
|
||||||
|
value: Text("${cfg.messagePageSize}"),
|
||||||
|
onPressed: (_) {
|
||||||
|
SettingsNumberModal.show(
|
||||||
|
context,
|
||||||
|
title: 'Page Size (Messages)',
|
||||||
|
currentValue: cfg.messagePageSize,
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 2048,
|
||||||
|
onValueChanged: (value) => AppSettings().update((p) => p.messagePageSize = value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: cfg.backgroundRefreshMessageListOnPop,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidPageCaretDown),
|
||||||
|
title: Text('Refresh messages on page navigation'),
|
||||||
|
onToggle: (value) => AppSettings().update((p) => p.backgroundRefreshMessageListOnPop = !p.backgroundRefreshMessageListOnPop),
|
||||||
|
),
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: cfg.alwaysBackgroundRefreshMessageListOnLifecycleResume,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidRecycle),
|
||||||
|
title: Text('Refresh messages on app resume'),
|
||||||
|
onToggle: (value) => AppSettings().update((p) => p.alwaysBackgroundRefreshMessageListOnLifecycleResume = !p.alwaysBackgroundRefreshMessageListOnLifecycleResume),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SettingsSection(
|
||||||
|
title: Text('About'),
|
||||||
|
tiles: [
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidCodeCommit),
|
||||||
|
title: Text('Version'),
|
||||||
|
value: Text(Globals().version),
|
||||||
|
onPressed: (cfg.devMode)
|
||||||
|
? null
|
||||||
|
: (context) {
|
||||||
|
if (_lastClickTime == null || DateTime.now().difference(_lastClickTime!).inSeconds > 1) _multiClickCounter = 0;
|
||||||
|
_multiClickCounter++;
|
||||||
|
_lastClickTime = DateTime.now();
|
||||||
|
|
||||||
|
if (_multiClickCounter >= 12) {
|
||||||
|
Toaster.info("Debug", "Developer mode enabled");
|
||||||
|
AppSettings().update((p) {
|
||||||
|
p.devMode = true;
|
||||||
|
p.showDebugButton = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidCodeBranch),
|
||||||
|
title: Text('Build'),
|
||||||
|
value: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(GitStamp.sha.substring(0, 7) + ' +' + Globals().buildNumber),
|
||||||
|
Text("( " + cfg.dateFormat.dateFormat().format(DateTime.parse(GitStamp.buildDateTime).toLocal()) + " )", style: TextStyle(fontStyle: FontStyle.italic)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: (context) => _clipboardCopy(GitStamp.sha),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidBell),
|
||||||
|
title: Text('FCM Token'),
|
||||||
|
value: Text(AppAuth().getToken()),
|
||||||
|
onPressed: (context) => _clipboardCopy(AppAuth().getToken()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clipboardCopy(String v) {
|
||||||
|
Clipboard.setData(new ClipboardData(text: v));
|
||||||
|
Toaster.info("Clipboard", 'Copied to Clipboard');
|
||||||
|
print('================= [CLIPBOARD] =================\n${v}\n================= [/CLIPBOARD] =================');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AbstractSettingsTile> _buildNotificationTiles(BuildContext context, AppSettings cfg, int prio) {
|
||||||
|
final ncf = AppSettings().getNotificationSettings(prio);
|
||||||
|
return [
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: ncf.enableLights,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidLightbulb),
|
||||||
|
title: Text('Enable Lights'),
|
||||||
|
onToggle: (value) => AppSettings().updateNotification(prio, (p) => p.withEnableLights(!p.enableLights)),
|
||||||
|
),
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: ncf.enableVibration,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidShutters),
|
||||||
|
title: Text('Enable Vibration'),
|
||||||
|
onToggle: (value) => AppSettings().updateNotification(prio, (p) => p.withEnableVibration(!p.enableVibration)),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidWaveform),
|
||||||
|
title: Text('Notification Sound'),
|
||||||
|
value: Text(ncf.sound ?? '(Default)'),
|
||||||
|
onPressed: (context) => {/*TODO*/},
|
||||||
|
),
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: ncf.playSound,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidVolume),
|
||||||
|
title: Text('Play Sound'),
|
||||||
|
onToggle: (value) => AppSettings().updateNotification(prio, (p) => p.withPlaySound(!p.playSound)),
|
||||||
|
),
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: ncf.silent,
|
||||||
|
leading: Icon(FontAwesomeIcons.solidVolumeSlash),
|
||||||
|
title: Text('Silent'),
|
||||||
|
onToggle: (value) => AppSettings().updateNotification(prio, (p) => p.withSilent(!p.silent)),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
leading: Icon(FontAwesomeIcons.solidStopwatch20),
|
||||||
|
title: Text('Auto Timeout'),
|
||||||
|
value: Text((ncf.timeoutAfter != null) ? "${ncf.timeoutAfter} sec" : "(None)"),
|
||||||
|
onPressed: (context) => {/*TODO*/},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:simplecloudnotifier/models/channel.dart';
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||||
@ -15,8 +14,6 @@ enum SubscriptionListItemMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SubscriptionListItem extends StatelessWidget {
|
class SubscriptionListItem extends StatelessWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
const SubscriptionListItem({
|
const SubscriptionListItem({
|
||||||
required this.item,
|
required this.item,
|
||||||
required this.userCache,
|
required this.userCache,
|
||||||
|
@ -10,6 +10,7 @@ import 'package:simplecloudnotifier/models/user.dart';
|
|||||||
import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart';
|
import 'package:simplecloudnotifier/pages/channel_view/channel_view.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||||
import 'package:simplecloudnotifier/utils/dialogs.dart';
|
import 'package:simplecloudnotifier/utils/dialogs.dart';
|
||||||
@ -40,8 +41,6 @@ enum EditState { none, editing, saving }
|
|||||||
enum SubscriptionViewPageInitState { loading, okay, error }
|
enum SubscriptionViewPageInitState { loading, okay, error }
|
||||||
|
|
||||||
class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd HH:mm'); //TODO setting
|
|
||||||
|
|
||||||
ImmediateFuture<UserPreview> _futureChannelOwner = ImmediateFuture.ofPending();
|
ImmediateFuture<UserPreview> _futureChannelOwner = ImmediateFuture.ofPending();
|
||||||
ImmediateFuture<UserPreview> _futureSubscriber = ImmediateFuture.ofPending();
|
ImmediateFuture<UserPreview> _futureSubscriber = ImmediateFuture.ofPending();
|
||||||
ImmediateFuture<ChannelPreview> _futureChannel = ImmediateFuture.ofPending();
|
ImmediateFuture<ChannelPreview> _futureChannel = ImmediateFuture.ofPending();
|
||||||
@ -161,6 +160,8 @@ class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOwnedSubscriptionView(BuildContext context, Subscription subscription) {
|
Widget _buildOwnedSubscriptionView(BuildContext context, Subscription subscription) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
||||||
@ -181,7 +182,7 @@ class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.clock,
|
icon: FontAwesomeIcons.clock,
|
||||||
title: 'Created',
|
title: 'Created',
|
||||||
values: [_SubscriptionViewPageState._dateFormat.format(DateTime.parse(subscription.timestampCreated).toLocal())],
|
values: [dateFormat.format(DateTime.parse(subscription.timestampCreated).toLocal())],
|
||||||
),
|
),
|
||||||
_buildStatusCard(context),
|
_buildStatusCard(context),
|
||||||
UI.button(text: "Unsubscribe", onPressed: _unsubscribe, tonal: true),
|
UI.button(text: "Unsubscribe", onPressed: _unsubscribe, tonal: true),
|
||||||
@ -192,6 +193,8 @@ class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildIncomingSubscriptionView(BuildContext context, Subscription subscription) {
|
Widget _buildIncomingSubscriptionView(BuildContext context, Subscription subscription) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
||||||
@ -212,7 +215,7 @@ class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.clock,
|
icon: FontAwesomeIcons.clock,
|
||||||
title: 'Created',
|
title: 'Created',
|
||||||
values: [_SubscriptionViewPageState._dateFormat.format(DateTime.parse(subscription.timestampCreated).toLocal())],
|
values: [dateFormat.format(DateTime.parse(subscription.timestampCreated).toLocal())],
|
||||||
),
|
),
|
||||||
_buildStatusCard(context),
|
_buildStatusCard(context),
|
||||||
if (subscription.confirmed) UI.button(text: "Revoke subscription", onPressed: _unsubscribe, color: Colors.red),
|
if (subscription.confirmed) UI.button(text: "Revoke subscription", onPressed: _unsubscribe, color: Colors.red),
|
||||||
@ -225,6 +228,8 @@ class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOutgoingSubscriptionView(BuildContext context, Subscription subscription) {
|
Widget _buildOutgoingSubscriptionView(BuildContext context, Subscription subscription) {
|
||||||
|
final dateFormat = context.select<AppSettings, AppSettingsDateFormat>((v) => v.dateFormat).dateFormat();
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
||||||
@ -245,7 +250,7 @@ class _SubscriptionViewPageState extends State<SubscriptionViewPage> {
|
|||||||
context: context,
|
context: context,
|
||||||
icon: FontAwesomeIcons.clock,
|
icon: FontAwesomeIcons.clock,
|
||||||
title: 'Created',
|
title: 'Created',
|
||||||
values: [_SubscriptionViewPageState._dateFormat.format(DateTime.parse(subscription.timestampCreated).toLocal())],
|
values: [dateFormat.format(DateTime.parse(subscription.timestampCreated).toLocal())],
|
||||||
),
|
),
|
||||||
_buildStatusCard(context),
|
_buildStatusCard(context),
|
||||||
if (subscription.confirmed && subscription.active) UI.button(text: "Deactivate subscription", onPressed: _deactivate, tonal: true),
|
if (subscription.confirmed && subscription.active) UI.button(text: "Deactivate subscription", onPressed: _deactivate, tonal: true),
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AppSettings extends ChangeNotifier {
|
|
||||||
bool groupNotifications = true;
|
|
||||||
int messagePageSize = 128;
|
|
||||||
bool showDebugButton = true;
|
|
||||||
bool backgroundRefreshMessageListOnPop = false;
|
|
||||||
bool alwaysBackgroundRefreshMessageListOnLifecycleResume = true;
|
|
||||||
|
|
||||||
static AppSettings? _singleton = AppSettings._internal();
|
|
||||||
|
|
||||||
factory AppSettings() {
|
|
||||||
return _singleton ?? (_singleton = AppSettings._internal());
|
|
||||||
}
|
|
||||||
|
|
||||||
AppSettings._internal() {
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
//TODO
|
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void load() {
|
|
||||||
//TODO
|
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> save() async {
|
|
||||||
//TODO
|
|
||||||
}
|
|
||||||
}
|
|
212
flutter/lib/state/app_settings.dart
Normal file
212
flutter/lib/state/app_settings.dart
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:shared_preferences/src/shared_preferences_legacy.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
|
|
||||||
|
enum AppSettingsDateFormat {
|
||||||
|
ISO(displayStr: 'ISO (yyyy-MM-dd)', key: 'ISO'),
|
||||||
|
German(displayStr: 'German (dd.MM.yyyy)', key: 'German'),
|
||||||
|
US(displayStr: 'US (MM/dd/yyyy)', key: 'US');
|
||||||
|
|
||||||
|
const AppSettingsDateFormat({required this.displayStr, required this.key});
|
||||||
|
|
||||||
|
final String displayStr;
|
||||||
|
final String key;
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => displayStr;
|
||||||
|
|
||||||
|
DateFormat dateFormat() {
|
||||||
|
switch (this) {
|
||||||
|
case AppSettingsDateFormat.ISO:
|
||||||
|
return DateFormat('yyyy-MM-dd HH:mm');
|
||||||
|
case AppSettingsDateFormat.German:
|
||||||
|
return DateFormat('dd.MM.yyyy HH:mm');
|
||||||
|
case AppSettingsDateFormat.US:
|
||||||
|
return DateFormat('MM/dd/yyyy HH:mm');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static AppSettingsDateFormat? parse(String? string) {
|
||||||
|
if (string == null) return null;
|
||||||
|
return values.firstWhere((e) => e.key == string, orElse: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppSettings extends ChangeNotifier {
|
||||||
|
bool groupNotifications = true;
|
||||||
|
int messagePageSize = 128;
|
||||||
|
bool devMode = false;
|
||||||
|
bool showDebugButton = false;
|
||||||
|
bool backgroundRefreshMessageListOnPop = false;
|
||||||
|
bool alwaysBackgroundRefreshMessageListOnLifecycleResume = true;
|
||||||
|
AppSettingsDateFormat dateFormat = AppSettingsDateFormat.ISO;
|
||||||
|
int messagePreviewLength = 3;
|
||||||
|
|
||||||
|
AppNotificationSettings notification0 = AppNotificationSettings();
|
||||||
|
AppNotificationSettings notification1 = AppNotificationSettings();
|
||||||
|
AppNotificationSettings notification2 = AppNotificationSettings();
|
||||||
|
|
||||||
|
static AppSettings? _singleton = AppSettings._internal();
|
||||||
|
|
||||||
|
factory AppSettings() {
|
||||||
|
return _singleton ?? (_singleton = AppSettings._internal());
|
||||||
|
}
|
||||||
|
|
||||||
|
AppSettings._internal() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
groupNotifications = true;
|
||||||
|
messagePageSize = 128;
|
||||||
|
devMode = false;
|
||||||
|
showDebugButton = false;
|
||||||
|
backgroundRefreshMessageListOnPop = false;
|
||||||
|
alwaysBackgroundRefreshMessageListOnLifecycleResume = true;
|
||||||
|
dateFormat = AppSettingsDateFormat.ISO;
|
||||||
|
messagePreviewLength = 3;
|
||||||
|
|
||||||
|
notification0 = AppNotificationSettings();
|
||||||
|
notification1 = AppNotificationSettings();
|
||||||
|
notification2 = AppNotificationSettings();
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
groupNotifications = Globals().sharedPrefs.getBool('settings.groupNotifications') ?? groupNotifications;
|
||||||
|
messagePageSize = Globals().sharedPrefs.getInt('settings.messagePageSize') ?? messagePageSize;
|
||||||
|
devMode = Globals().sharedPrefs.getBool('settings.devMode') ?? devMode;
|
||||||
|
showDebugButton = Globals().sharedPrefs.getBool('settings.showDebugButton') ?? showDebugButton;
|
||||||
|
backgroundRefreshMessageListOnPop = Globals().sharedPrefs.getBool('settings.backgroundRefreshMessageListOnPop') ?? backgroundRefreshMessageListOnPop;
|
||||||
|
alwaysBackgroundRefreshMessageListOnLifecycleResume = Globals().sharedPrefs.getBool('settings.alwaysBackgroundRefreshMessageListOnLifecycleResume') ?? alwaysBackgroundRefreshMessageListOnLifecycleResume;
|
||||||
|
dateFormat = AppSettingsDateFormat.parse(Globals().sharedPrefs.getString('settings.dateFormat')) ?? dateFormat;
|
||||||
|
messagePreviewLength = Globals().sharedPrefs.getInt('settings.messagePreviewLength') ?? messagePreviewLength;
|
||||||
|
|
||||||
|
notification0 = AppNotificationSettings.load(Globals().sharedPrefs, 'settings.notification0');
|
||||||
|
notification1 = AppNotificationSettings.load(Globals().sharedPrefs, 'settings.notification1');
|
||||||
|
notification2 = AppNotificationSettings.load(Globals().sharedPrefs, 'settings.notification2');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async {
|
||||||
|
await Globals().sharedPrefs.setBool('settings.groupNotifications', groupNotifications);
|
||||||
|
await Globals().sharedPrefs.setInt('settings.messagePageSize', messagePageSize);
|
||||||
|
await Globals().sharedPrefs.setBool('settings.devMode', devMode);
|
||||||
|
await Globals().sharedPrefs.setBool('settings.showDebugButton', showDebugButton);
|
||||||
|
await Globals().sharedPrefs.setBool('settings.backgroundRefreshMessageListOnPop', backgroundRefreshMessageListOnPop);
|
||||||
|
await Globals().sharedPrefs.setBool('settings.alwaysBackgroundRefreshMessageListOnLifecycleResume', alwaysBackgroundRefreshMessageListOnLifecycleResume);
|
||||||
|
await Globals().sharedPrefs.setString('settings.dateFormat', dateFormat.key);
|
||||||
|
await Globals().sharedPrefs.setInt('settings.messagePreviewLength', messagePreviewLength);
|
||||||
|
|
||||||
|
await notification0.save(Globals().sharedPrefs, 'settings.notification0');
|
||||||
|
await notification1.save(Globals().sharedPrefs, 'settings.notification1');
|
||||||
|
await notification2.save(Globals().sharedPrefs, 'settings.notification2');
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(void Function(AppSettings p) fn) {
|
||||||
|
fn(this);
|
||||||
|
save();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateNotification(int prio, AppNotificationSettings Function(AppNotificationSettings p) fn) {
|
||||||
|
if (prio == 0) {
|
||||||
|
notification0 = fn(notification0);
|
||||||
|
} else if (prio == 1) {
|
||||||
|
notification1 = fn(notification1);
|
||||||
|
} else if (prio == 2) {
|
||||||
|
notification2 = fn(notification2);
|
||||||
|
}
|
||||||
|
|
||||||
|
save();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppNotificationSettings getNotificationSettings(int? prio) {
|
||||||
|
if (prio != null && prio == 0) {
|
||||||
|
return notification0;
|
||||||
|
} else if (prio != null && prio == 1) {
|
||||||
|
return notification1;
|
||||||
|
} else if (prio != null && prio == 2) {
|
||||||
|
return notification2;
|
||||||
|
} else {
|
||||||
|
return AppNotificationSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppNotificationSettings {
|
||||||
|
// Immutable
|
||||||
|
AppNotificationSettings({
|
||||||
|
this.enableLights = false,
|
||||||
|
this.enableVibration = true,
|
||||||
|
this.playSound = true,
|
||||||
|
this.sound = null,
|
||||||
|
this.silent = false,
|
||||||
|
this.timeoutAfter = null,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool enableLights;
|
||||||
|
final bool enableVibration;
|
||||||
|
final bool playSound;
|
||||||
|
final String? sound;
|
||||||
|
final bool silent;
|
||||||
|
final int? timeoutAfter;
|
||||||
|
|
||||||
|
Future<void> save(SharedPreferences sharedPrefs, String prefix) async {
|
||||||
|
await Globals().sharedPrefs.setBool('${prefix}.enableLights', enableLights);
|
||||||
|
await Globals().sharedPrefs.setBool('${prefix}.enableVibration', enableVibration);
|
||||||
|
await Globals().sharedPrefs.setBool('${prefix}.playSound', playSound);
|
||||||
|
await Globals().sharedPrefs.setString('${prefix}.sound', _encode(sound));
|
||||||
|
await Globals().sharedPrefs.setBool('${prefix}.silent', silent);
|
||||||
|
await Globals().sharedPrefs.setString('${prefix}.timeoutAfter', _encode(timeoutAfter));
|
||||||
|
}
|
||||||
|
|
||||||
|
UriAndroidNotificationSound? soundURI() {
|
||||||
|
return (sound != null) ? UriAndroidNotificationSound(sound!) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppNotificationSettings withEnableLights(bool v) => AppNotificationSettings(enableLights: v, enableVibration: enableVibration, playSound: playSound, sound: sound, silent: silent, timeoutAfter: timeoutAfter);
|
||||||
|
AppNotificationSettings withEnableVibration(bool v) => AppNotificationSettings(enableLights: enableLights, enableVibration: v, playSound: playSound, sound: sound, silent: silent, timeoutAfter: timeoutAfter);
|
||||||
|
AppNotificationSettings withPlaySound(bool v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: v, sound: sound, silent: silent, timeoutAfter: timeoutAfter);
|
||||||
|
AppNotificationSettings withSound(String? v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: playSound, sound: v, silent: silent, timeoutAfter: timeoutAfter);
|
||||||
|
AppNotificationSettings withSilent(bool v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: playSound, sound: sound, silent: v, timeoutAfter: timeoutAfter);
|
||||||
|
AppNotificationSettings withTimeoutAfter(int? v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: playSound, sound: sound, silent: silent, timeoutAfter: v);
|
||||||
|
|
||||||
|
static AppNotificationSettings load(SharedPreferences prefs, String prefix) {
|
||||||
|
final def = AppNotificationSettings();
|
||||||
|
|
||||||
|
final enableLights = prefs.getBool('${prefix}.enableLights') ?? def.enableLights;
|
||||||
|
final enableVibration = prefs.getBool('${prefix}.enableVibration') ?? def.enableVibration;
|
||||||
|
final playSound = prefs.getBool('${prefix}.playSound') ?? def.playSound;
|
||||||
|
final sound = _decode(prefs.getString('${prefix}.sound'), def.sound);
|
||||||
|
final silent = prefs.getBool('${prefix}.silent') ?? def.silent;
|
||||||
|
final timeoutAfter = _decode(prefs.getString('${prefix}.timeoutAfter'), def.timeoutAfter);
|
||||||
|
|
||||||
|
return AppNotificationSettings(
|
||||||
|
enableLights: enableLights,
|
||||||
|
enableVibration: enableVibration,
|
||||||
|
playSound: playSound,
|
||||||
|
sound: sound,
|
||||||
|
silent: silent,
|
||||||
|
timeoutAfter: timeoutAfter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _encode<T>(T v) {
|
||||||
|
return JsonEncoder().convert(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
T _decode<T>(String? v, T fallback) {
|
||||||
|
if (v == null) return fallback;
|
||||||
|
try {
|
||||||
|
return JsonDecoder().convert(v) as T;
|
||||||
|
} catch (_) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,87 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
|
|
||||||
|
enum ThemeColor {
|
||||||
|
Pink(displayStr: 'Pink', key: 'PINK', value: Colors.pink),
|
||||||
|
Red(displayStr: 'Red', key: 'RED', value: Colors.red),
|
||||||
|
DeepOrange(displayStr: 'Deep-Orange', key: 'DEEPORANGE', value: Colors.deepOrange),
|
||||||
|
Orange(displayStr: 'Orange', key: 'ORANGE', value: Colors.orange),
|
||||||
|
Amber(displayStr: 'Amber', key: 'AMBER', value: Colors.amber),
|
||||||
|
Yellow(displayStr: 'Yellow', key: 'YELLOW', value: Colors.yellow),
|
||||||
|
Lime(displayStr: 'Lime', key: 'LIME', value: Colors.lime),
|
||||||
|
LightGreen(displayStr: 'Light-Green', key: 'LIGHTGREEN', value: Colors.lightGreen),
|
||||||
|
Green(displayStr: 'Green', key: 'GREEN', value: Colors.green),
|
||||||
|
Teal(displayStr: 'Teal', key: 'TEAL', value: Colors.teal),
|
||||||
|
Cyan(displayStr: 'Cyan', key: 'CYAN', value: Colors.cyan),
|
||||||
|
LightBlue(displayStr: 'Light-Blue', key: 'LIGHTBLUE', value: Colors.lightBlue),
|
||||||
|
Blue(displayStr: 'Blue', key: 'BLUE', value: Colors.blue),
|
||||||
|
Indigo(displayStr: 'Indigo', key: 'INDIGO', value: Colors.indigo),
|
||||||
|
Purple(displayStr: 'Purple', key: 'PURPLE', value: Colors.purple),
|
||||||
|
DeepPurple(displayStr: 'Deep-Purple', key: 'DEEPPURPLE', value: Colors.deepPurple),
|
||||||
|
BlueGrey(displayStr: 'Blue-Grey', key: 'BLUEGREY', value: Colors.blueGrey),
|
||||||
|
Brown(displayStr: 'Brown', key: 'BROWN', value: Colors.brown),
|
||||||
|
Grey(displayStr: 'Grey', key: 'GREY', value: Colors.grey);
|
||||||
|
|
||||||
|
const ThemeColor({required this.displayStr, required this.key, required this.value});
|
||||||
|
|
||||||
|
final String displayStr;
|
||||||
|
final String key;
|
||||||
|
final Color value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => displayStr;
|
||||||
|
|
||||||
|
static ThemeColor? parse(String? string) {
|
||||||
|
if (string == null) return null;
|
||||||
|
return values.firstWhere((e) => e.key == string, orElse: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AppTheme extends ChangeNotifier {
|
class AppTheme extends ChangeNotifier {
|
||||||
|
static AppTheme? _singleton = AppTheme._internal();
|
||||||
|
|
||||||
|
factory AppTheme() {
|
||||||
|
return _singleton ?? (_singleton = AppTheme._internal());
|
||||||
|
}
|
||||||
|
|
||||||
|
AppTheme._internal() {}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
bool _darkmode = false;
|
bool _darkmode = false;
|
||||||
bool get darkMode => _darkmode;
|
bool get darkMode => _darkmode;
|
||||||
|
|
||||||
|
ThemeColor _color = ThemeColor.Blue;
|
||||||
|
ThemeColor get color => _color;
|
||||||
|
|
||||||
void setDarkMode(bool v) {
|
void setDarkMode(bool v) {
|
||||||
_darkmode = v;
|
_darkmode = v;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchDarkMode() {
|
void switchDarkMode() {
|
||||||
_darkmode = !_darkmode;
|
_darkmode = !_darkmode;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColor(ThemeColor v) {
|
||||||
|
_color = v;
|
||||||
|
notifyListeners();
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
_darkmode = Globals().sharedPrefs.getBool('theme.dark') ?? _darkmode;
|
||||||
|
_color = ThemeColor.parse(Globals().sharedPrefs.getString('theme.color')) ?? _color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async {
|
||||||
|
await Globals().sharedPrefs.setBool('theme.dark', _darkmode);
|
||||||
|
await Globals().sharedPrefs.setString('theme.color', _color.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import 'package:simplecloudnotifier/api/api_client.dart';
|
|||||||
import 'package:simplecloudnotifier/models/channel.dart';
|
import 'package:simplecloudnotifier/models/channel.dart';
|
||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
|
|
||||||
class SCNDataCache {
|
class SCNDataCache {
|
||||||
SCNDataCache._internal();
|
SCNDataCache._internal();
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:simplecloudnotifier/settings/app_settings.dart';
|
import 'package:simplecloudnotifier/state/app_settings.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
|
|
||||||
class Notifier {
|
class Notifier {
|
||||||
static void showLocalNotification(String messageID, String channelID, String channelName, String channelDescr, String title, String body, DateTime? timestamp) async {
|
static void showLocalNotification(String messageID, String channelID, String channelName, String channelDescr, String title, String body, DateTime? timestamp, int? prio) async {
|
||||||
final nid = Globals().sharedPrefs.getInt('notifier.nextid') ?? 1000;
|
final nid = Globals().sharedPrefs.getInt('notifier.nextid') ?? 1000;
|
||||||
Globals().sharedPrefs.setInt('notifier.nextid', nid + 7);
|
Globals().sharedPrefs.setInt('notifier.nextid', nid + 7);
|
||||||
|
|
||||||
@ -60,6 +60,8 @@ class Notifier {
|
|||||||
payload = ['@SCN_MESSAGE', messageID, channelID, newMessageNID].join("\n");
|
payload = ['@SCN_MESSAGE', messageID, channelID, newMessageNID].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final cfg = AppSettings().getNotificationSettings(prio);
|
||||||
|
|
||||||
// ======== SHOW NOTIFICATION ========
|
// ======== SHOW NOTIFICATION ========
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
newMessageNID,
|
newMessageNID,
|
||||||
@ -75,6 +77,12 @@ class Notifier {
|
|||||||
when: timestamp?.millisecondsSinceEpoch,
|
when: timestamp?.millisecondsSinceEpoch,
|
||||||
groupKey: channelID,
|
groupKey: channelID,
|
||||||
subText: (channelName == 'main') ? null : channelName,
|
subText: (channelName == 'main') ? null : channelName,
|
||||||
|
enableLights: cfg.enableLights,
|
||||||
|
enableVibration: cfg.enableVibration,
|
||||||
|
playSound: cfg.playSound,
|
||||||
|
sound: cfg.soundURI(),
|
||||||
|
silent: cfg.silent,
|
||||||
|
timeoutAfter: cfg.timeoutAfter,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
payload: payload,
|
payload: payload,
|
||||||
|
@ -46,6 +46,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "2.7.0"
|
||||||
|
aron_gradient_line:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: aron_gradient_line
|
||||||
|
sha256: "6b0df835c33bc4226f6984321f709394aa4734d9729ad5a0411d1add9df8e9d2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
asn1lib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: asn1lib
|
||||||
|
sha256: e02d018628c870ef2d7f03e33f9ad179d89ff6ec52ca6c56bcb80bcef979867f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.2"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -238,6 +254,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.2"
|
version: "7.0.2"
|
||||||
|
encrypt:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: encrypt
|
||||||
|
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.3"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -326,6 +350,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
fl_chart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fl_chart
|
||||||
|
sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.69.2"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -412,6 +444,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
git_stamp:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: git_stamp
|
||||||
|
sha256: eddda29d15136503af57b5d927393fab2e7fe9660d4dc9ae418eb69c3f542c30
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.10.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -740,6 +780,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pointycastle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointycastle
|
||||||
|
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.9.1"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -796,6 +844,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
settings_ui:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: settings_ui
|
||||||
|
sha256: d9838037cb554b24b4218b2d07666fbada3478882edefae375ee892b6c820ef3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -2,7 +2,7 @@ name: simplecloudnotifier
|
|||||||
description: "Receive push messages"
|
description: "Receive push messages"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 2.0.0+100
|
version: 2.0.0+474
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.2.6 <4.0.0'
|
sdk: '>=3.2.6 <4.0.0'
|
||||||
@ -38,6 +38,8 @@ dependencies:
|
|||||||
|
|
||||||
path: any
|
path: any
|
||||||
mobile_scanner: ^6.0.1
|
mobile_scanner: ^6.0.1
|
||||||
|
settings_ui: ^2.0.2
|
||||||
|
git_stamp: ^5.10.0
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
font_awesome_flutter:
|
font_awesome_flutter:
|
||||||
path: deps/font_awesome_flutter
|
path: deps/font_awesome_flutter
|
||||||
|
Loading…
x
Reference in New Issue
Block a user