home layout

This commit is contained in:
Christien Rioux 2023-07-28 20:36:05 -04:00
parent f754f7d5ed
commit ca6b00e021
45 changed files with 2168 additions and 561 deletions

View File

@ -5,6 +5,11 @@
"app_bar": {
"settings_tooltip": "Settings"
},
"pager": {
"account": "Account",
"chats": "Chats",
"contacts": "Contacts"
},
"account": {
"form_name": "Name",
"form_title": "Title (optional)",
@ -22,25 +27,26 @@
},
"button": {
"ok": "Ok",
"cancel": "Cancel",
"change_language": "Change Language"
"cancel": "Cancel"
},
"language": {
"name": {
"en": "English"
},
"change_language": "Change Language",
"selected_message": "Currently selected language is {language}",
"selection": {
"message": "Please select a language from the list",
"title": "Language Selection"
}
},
"plural": {
"demo": {
"zero": "Please start pushing the 'plus' button.",
"one": "You have pushed the button one time.",
"two": "You have pushed the button two times.",
"other": "You have pushed the button {{value}} times."
}
"account_page": {
"missing_account_title": "Missing Account",
"missing_account_text": "Account is missing, removing from list",
"invalid_account_title": "Missing Account",
"invalid_account_text": "Account is missing, removing from list"
},
"themes": {
"vapor": "Vapor"
}
}

View File

@ -5,21 +5,45 @@ class Chat extends ConsumerWidget {
const Chat({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => Scaffold(
appBar: AppBar(title: const Text('Chat')),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context, WidgetRef ref) {
//
return Align(
alignment: AlignmentDirectional.centerEnd,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
),
child: Stack(
children: [
Text('Home Page'),
// ElevatedButton(
// onPressed: () {
// ref.watch(authNotifierProvider.notifier).logout();
// },
// child: const Text("Logout"),
// ),
Column(
children: [
Container(
height: 48,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
),
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Padding(
padding:
const EdgeInsetsDirectional.fromSTEB(16, 0, 16, 0),
child: Text("current contact",
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.titleMedium),
),
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(),
child: Text("Chat"),
),
),
],
),
],
),
);
));
}
}

View File

@ -0,0 +1,25 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ChatList extends ConsumerWidget {
const ChatList({super.key});
//final LocalAccount account;
@override
Widget build(BuildContext context, WidgetRef ref) {
//final logins = ref.watch(loginsProvider);
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [const Expanded(child: Text('Chat List'))]));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
//properties.add(DiagnosticsProperty<LocalAccount>('account', account));
}
}

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class EmptyChatComponentWidget extends ConsumerWidget {
const EmptyChatComponentWidget({super.key});
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context, WidgetRef ref) {
//
return Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.chat,
color: Theme.of(context).disabledColor,
size: 48,
),
Text(
'Say Something',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).disabledColor,
),
),
],
),
);
}
}

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class EmptyContactListComponentWidget extends ConsumerWidget {
const EmptyContactListComponentWidget({super.key});
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context, WidgetRef ref) {
//
return Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.group_add,
color: Theme.of(context).disabledColor,
size: 48,
),
Text(
'Start A Conversation',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).disabledColor,
),
),
],
),
);
}
}

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class NoContactComponentWidget extends ConsumerWidget {
const NoContactComponentWidget({super.key});
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context, WidgetRef ref) {
//
return Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.emoji_people_outlined,
color: Theme.of(context).disabledColor,
size: 48,
),
Text(
'Choose A Conversation To Chat',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).disabledColor,
),
),
],
),
);
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ProfileWidget extends ConsumerWidget {
const ProfileWidget({
required this.name,
this.title,
super.key,
});
final String name;
final String? title;
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context, WidgetRef ref) {
// final logins = ref.watch(loginsProvider);
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300),
child: Column(children: [
Text('Profile', style: Theme.of(context).textTheme.headlineMedium),
Text(name, style: Theme.of(context).textTheme.bodyMedium),
if (title != null && title!.isNotEmpty)
Text(title!, style: Theme.of(context).textTheme.bodySmall),
]));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(StringProperty('name', name))
..add(StringProperty('title', title));
}
}

View File

@ -48,6 +48,8 @@ class LocalAccount with _$LocalAccount {
// Keep account hidden unless account password is entered
// (tries all hidden accounts with auth method (no biometrics))
required bool hiddenAccount,
// Display name for account until it is unlocked
required String name,
}) = _LocalAccount;
factory LocalAccount.fromJson(dynamic json) =>

View File

@ -34,7 +34,9 @@ mixin _$LocalAccount {
bool get biometricsEnabled =>
throw _privateConstructorUsedError; // Keep account hidden unless account password is entered
// (tries all hidden accounts with auth method (no biometrics))
bool get hiddenAccount => throw _privateConstructorUsedError;
bool get hiddenAccount =>
throw _privateConstructorUsedError; // Display name for account until it is unlocked
String get name => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@ -54,7 +56,8 @@ abstract class $LocalAccountCopyWith<$Res> {
@Uint8ListJsonConverter() Uint8List identitySecretSaltBytes,
EncryptionKeyType encryptionKeyType,
bool biometricsEnabled,
bool hiddenAccount});
bool hiddenAccount,
String name});
$IdentityMasterCopyWith<$Res> get identityMaster;
}
@ -78,6 +81,7 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
Object? encryptionKeyType = null,
Object? biometricsEnabled = null,
Object? hiddenAccount = null,
Object? name = null,
}) {
return _then(_value.copyWith(
identityMaster: null == identityMaster
@ -104,6 +108,10 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
? _value.hiddenAccount
: hiddenAccount // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
@ -130,7 +138,8 @@ abstract class _$$_LocalAccountCopyWith<$Res>
@Uint8ListJsonConverter() Uint8List identitySecretSaltBytes,
EncryptionKeyType encryptionKeyType,
bool biometricsEnabled,
bool hiddenAccount});
bool hiddenAccount,
String name});
@override
$IdentityMasterCopyWith<$Res> get identityMaster;
@ -153,6 +162,7 @@ class __$$_LocalAccountCopyWithImpl<$Res>
Object? encryptionKeyType = null,
Object? biometricsEnabled = null,
Object? hiddenAccount = null,
Object? name = null,
}) {
return _then(_$_LocalAccount(
identityMaster: null == identityMaster
@ -179,6 +189,10 @@ class __$$_LocalAccountCopyWithImpl<$Res>
? _value.hiddenAccount
: hiddenAccount // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
@ -192,7 +206,8 @@ class _$_LocalAccount implements _LocalAccount {
@Uint8ListJsonConverter() required this.identitySecretSaltBytes,
required this.encryptionKeyType,
required this.biometricsEnabled,
required this.hiddenAccount});
required this.hiddenAccount,
required this.name});
factory _$_LocalAccount.fromJson(Map<String, dynamic> json) =>
_$$_LocalAccountFromJson(json);
@ -218,10 +233,13 @@ class _$_LocalAccount implements _LocalAccount {
// (tries all hidden accounts with auth method (no biometrics))
@override
final bool hiddenAccount;
// Display name for account until it is unlocked
@override
final String name;
@override
String toString() {
return 'LocalAccount(identityMaster: $identityMaster, identitySecretKeyBytes: $identitySecretKeyBytes, identitySecretSaltBytes: $identitySecretSaltBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount)';
return 'LocalAccount(identityMaster: $identityMaster, identitySecretKeyBytes: $identitySecretKeyBytes, identitySecretSaltBytes: $identitySecretSaltBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
}
@override
@ -240,7 +258,8 @@ class _$_LocalAccount implements _LocalAccount {
(identical(other.biometricsEnabled, biometricsEnabled) ||
other.biometricsEnabled == biometricsEnabled) &&
(identical(other.hiddenAccount, hiddenAccount) ||
other.hiddenAccount == hiddenAccount));
other.hiddenAccount == hiddenAccount) &&
(identical(other.name, name) || other.name == name));
}
@JsonKey(ignore: true)
@ -252,7 +271,8 @@ class _$_LocalAccount implements _LocalAccount {
const DeepCollectionEquality().hash(identitySecretSaltBytes),
encryptionKeyType,
biometricsEnabled,
hiddenAccount);
hiddenAccount,
name);
@JsonKey(ignore: true)
@override
@ -276,7 +296,8 @@ abstract class _LocalAccount implements LocalAccount {
required final Uint8List identitySecretSaltBytes,
required final EncryptionKeyType encryptionKeyType,
required final bool biometricsEnabled,
required final bool hiddenAccount}) = _$_LocalAccount;
required final bool hiddenAccount,
required final String name}) = _$_LocalAccount;
factory _LocalAccount.fromJson(Map<String, dynamic> json) =
_$_LocalAccount.fromJson;
@ -296,6 +317,8 @@ abstract class _LocalAccount implements LocalAccount {
@override // Keep account hidden unless account password is entered
// (tries all hidden accounts with auth method (no biometrics))
bool get hiddenAccount;
@override // Display name for account until it is unlocked
String get name;
@override
@JsonKey(ignore: true)
_$$_LocalAccountCopyWith<_$_LocalAccount> get copyWith =>

View File

@ -17,6 +17,7 @@ _$_LocalAccount _$$_LocalAccountFromJson(Map<String, dynamic> json) =>
EncryptionKeyType.fromJson(json['encryption_key_type']),
biometricsEnabled: json['biometrics_enabled'] as bool,
hiddenAccount: json['hidden_account'] as bool,
name: json['name'] as String,
);
Map<String, dynamic> _$$_LocalAccountToJson(_$_LocalAccount instance) =>
@ -29,4 +30,5 @@ Map<String, dynamic> _$$_LocalAccountToJson(_$_LocalAccount instance) =>
'encryption_key_type': instance.encryptionKeyType.toJson(),
'biometrics_enabled': instance.biometricsEnabled,
'hidden_account': instance.hiddenAccount,
'name': instance.name,
};

View File

@ -6,13 +6,13 @@ part 'preferences.g.dart';
// Theme supports light and dark mode, optionally selected by the
// operating system
enum DarkModePreference {
enum BrightnessPreference {
system,
light,
dark;
factory DarkModePreference.fromJson(dynamic j) =>
DarkModePreference.values.byName((j as String).toCamelCase());
factory BrightnessPreference.fromJson(dynamic j) =>
BrightnessPreference.values.byName((j as String).toCamelCase());
String toJson() => name.toPascalCase();
}
@ -33,34 +33,20 @@ class LockPreference with _$LockPreference {
// Theme supports multiple color variants based on 'Radix'
enum ColorPreference {
amber,
blue,
bronze,
brown,
crimson,
cyan,
// Radix Colors
scarlet,
babydoll,
vapor,
gold,
grass,
gray,
green,
indigo,
garden,
forest,
arctic,
lapis,
eggplant,
lime,
mauve,
mint,
olive,
orange,
pink,
plum,
purple,
red,
sage,
sand,
sky,
slate,
teal,
tomato,
violet,
yellow;
grim,
// Accessible Colors
contrast;
factory ColorPreference.fromJson(dynamic j) =>
ColorPreference.values.byName((j as String).toCamelCase());
@ -76,15 +62,25 @@ enum LanguagePreference {
String toJson() => name.toPascalCase();
}
@freezed
class ThemePreferences with _$ThemePreferences {
const factory ThemePreferences({
required BrightnessPreference brightnessPreference,
required ColorPreference colorPreference,
required double displayScale,
}) = _ThemePreferences;
factory ThemePreferences.fromJson(dynamic json) =>
_$ThemePreferencesFromJson(json as Map<String, dynamic>);
}
// Preferences are stored in a table locally and globally affect all
// accounts imported/added and the app in general
@freezed
class Preferences with _$Preferences {
const factory Preferences({
required DarkModePreference darkMode,
required ColorPreference themeColor,
required ThemePreferences themePreferences,
required LanguagePreference language,
required int displayScale,
required LockPreference locking,
}) = _Preferences;

View File

@ -198,16 +198,199 @@ abstract class _LockPreference implements LockPreference {
throw _privateConstructorUsedError;
}
ThemePreferences _$ThemePreferencesFromJson(Map<String, dynamic> json) {
return _ThemePreferences.fromJson(json);
}
/// @nodoc
mixin _$ThemePreferences {
BrightnessPreference get brightnessPreference =>
throw _privateConstructorUsedError;
ColorPreference get colorPreference => throw _privateConstructorUsedError;
double get displayScale => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ThemePreferencesCopyWith<ThemePreferences> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ThemePreferencesCopyWith<$Res> {
factory $ThemePreferencesCopyWith(
ThemePreferences value, $Res Function(ThemePreferences) then) =
_$ThemePreferencesCopyWithImpl<$Res, ThemePreferences>;
@useResult
$Res call(
{BrightnessPreference brightnessPreference,
ColorPreference colorPreference,
double displayScale});
}
/// @nodoc
class _$ThemePreferencesCopyWithImpl<$Res, $Val extends ThemePreferences>
implements $ThemePreferencesCopyWith<$Res> {
_$ThemePreferencesCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? brightnessPreference = null,
Object? colorPreference = null,
Object? displayScale = null,
}) {
return _then(_value.copyWith(
brightnessPreference: null == brightnessPreference
? _value.brightnessPreference
: brightnessPreference // ignore: cast_nullable_to_non_nullable
as BrightnessPreference,
colorPreference: null == colorPreference
? _value.colorPreference
: colorPreference // ignore: cast_nullable_to_non_nullable
as ColorPreference,
displayScale: null == displayScale
? _value.displayScale
: displayScale // ignore: cast_nullable_to_non_nullable
as double,
) as $Val);
}
}
/// @nodoc
abstract class _$$_ThemePreferencesCopyWith<$Res>
implements $ThemePreferencesCopyWith<$Res> {
factory _$$_ThemePreferencesCopyWith(
_$_ThemePreferences value, $Res Function(_$_ThemePreferences) then) =
__$$_ThemePreferencesCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{BrightnessPreference brightnessPreference,
ColorPreference colorPreference,
double displayScale});
}
/// @nodoc
class __$$_ThemePreferencesCopyWithImpl<$Res>
extends _$ThemePreferencesCopyWithImpl<$Res, _$_ThemePreferences>
implements _$$_ThemePreferencesCopyWith<$Res> {
__$$_ThemePreferencesCopyWithImpl(
_$_ThemePreferences _value, $Res Function(_$_ThemePreferences) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? brightnessPreference = null,
Object? colorPreference = null,
Object? displayScale = null,
}) {
return _then(_$_ThemePreferences(
brightnessPreference: null == brightnessPreference
? _value.brightnessPreference
: brightnessPreference // ignore: cast_nullable_to_non_nullable
as BrightnessPreference,
colorPreference: null == colorPreference
? _value.colorPreference
: colorPreference // ignore: cast_nullable_to_non_nullable
as ColorPreference,
displayScale: null == displayScale
? _value.displayScale
: displayScale // ignore: cast_nullable_to_non_nullable
as double,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_ThemePreferences implements _ThemePreferences {
const _$_ThemePreferences(
{required this.brightnessPreference,
required this.colorPreference,
required this.displayScale});
factory _$_ThemePreferences.fromJson(Map<String, dynamic> json) =>
_$$_ThemePreferencesFromJson(json);
@override
final BrightnessPreference brightnessPreference;
@override
final ColorPreference colorPreference;
@override
final double displayScale;
@override
String toString() {
return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_ThemePreferences &&
(identical(other.brightnessPreference, brightnessPreference) ||
other.brightnessPreference == brightnessPreference) &&
(identical(other.colorPreference, colorPreference) ||
other.colorPreference == colorPreference) &&
(identical(other.displayScale, displayScale) ||
other.displayScale == displayScale));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, brightnessPreference, colorPreference, displayScale);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_ThemePreferencesCopyWith<_$_ThemePreferences> get copyWith =>
__$$_ThemePreferencesCopyWithImpl<_$_ThemePreferences>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_ThemePreferencesToJson(
this,
);
}
}
abstract class _ThemePreferences implements ThemePreferences {
const factory _ThemePreferences(
{required final BrightnessPreference brightnessPreference,
required final ColorPreference colorPreference,
required final double displayScale}) = _$_ThemePreferences;
factory _ThemePreferences.fromJson(Map<String, dynamic> json) =
_$_ThemePreferences.fromJson;
@override
BrightnessPreference get brightnessPreference;
@override
ColorPreference get colorPreference;
@override
double get displayScale;
@override
@JsonKey(ignore: true)
_$$_ThemePreferencesCopyWith<_$_ThemePreferences> get copyWith =>
throw _privateConstructorUsedError;
}
Preferences _$PreferencesFromJson(Map<String, dynamic> json) {
return _Preferences.fromJson(json);
}
/// @nodoc
mixin _$Preferences {
DarkModePreference get darkMode => throw _privateConstructorUsedError;
ColorPreference get themeColor => throw _privateConstructorUsedError;
ThemePreferences get themePreferences => throw _privateConstructorUsedError;
LanguagePreference get language => throw _privateConstructorUsedError;
int get displayScale => throw _privateConstructorUsedError;
LockPreference get locking => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -223,12 +406,11 @@ abstract class $PreferencesCopyWith<$Res> {
_$PreferencesCopyWithImpl<$Res, Preferences>;
@useResult
$Res call(
{DarkModePreference darkMode,
ColorPreference themeColor,
{ThemePreferences themePreferences,
LanguagePreference language,
int displayScale,
LockPreference locking});
$ThemePreferencesCopyWith<$Res> get themePreferences;
$LockPreferenceCopyWith<$Res> get locking;
}
@ -245,29 +427,19 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? darkMode = null,
Object? themeColor = null,
Object? themePreferences = null,
Object? language = null,
Object? displayScale = null,
Object? locking = null,
}) {
return _then(_value.copyWith(
darkMode: null == darkMode
? _value.darkMode
: darkMode // ignore: cast_nullable_to_non_nullable
as DarkModePreference,
themeColor: null == themeColor
? _value.themeColor
: themeColor // ignore: cast_nullable_to_non_nullable
as ColorPreference,
themePreferences: null == themePreferences
? _value.themePreferences
: themePreferences // ignore: cast_nullable_to_non_nullable
as ThemePreferences,
language: null == language
? _value.language
: language // ignore: cast_nullable_to_non_nullable
as LanguagePreference,
displayScale: null == displayScale
? _value.displayScale
: displayScale // ignore: cast_nullable_to_non_nullable
as int,
locking: null == locking
? _value.locking
: locking // ignore: cast_nullable_to_non_nullable
@ -275,6 +447,14 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences>
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$ThemePreferencesCopyWith<$Res> get themePreferences {
return $ThemePreferencesCopyWith<$Res>(_value.themePreferences, (value) {
return _then(_value.copyWith(themePreferences: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$LockPreferenceCopyWith<$Res> get locking {
@ -293,12 +473,12 @@ abstract class _$$_PreferencesCopyWith<$Res>
@override
@useResult
$Res call(
{DarkModePreference darkMode,
ColorPreference themeColor,
{ThemePreferences themePreferences,
LanguagePreference language,
int displayScale,
LockPreference locking});
@override
$ThemePreferencesCopyWith<$Res> get themePreferences;
@override
$LockPreferenceCopyWith<$Res> get locking;
}
@ -314,29 +494,19 @@ class __$$_PreferencesCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? darkMode = null,
Object? themeColor = null,
Object? themePreferences = null,
Object? language = null,
Object? displayScale = null,
Object? locking = null,
}) {
return _then(_$_Preferences(
darkMode: null == darkMode
? _value.darkMode
: darkMode // ignore: cast_nullable_to_non_nullable
as DarkModePreference,
themeColor: null == themeColor
? _value.themeColor
: themeColor // ignore: cast_nullable_to_non_nullable
as ColorPreference,
themePreferences: null == themePreferences
? _value.themePreferences
: themePreferences // ignore: cast_nullable_to_non_nullable
as ThemePreferences,
language: null == language
? _value.language
: language // ignore: cast_nullable_to_non_nullable
as LanguagePreference,
displayScale: null == displayScale
? _value.displayScale
: displayScale // ignore: cast_nullable_to_non_nullable
as int,
locking: null == locking
? _value.locking
: locking // ignore: cast_nullable_to_non_nullable
@ -349,29 +519,23 @@ class __$$_PreferencesCopyWithImpl<$Res>
@JsonSerializable()
class _$_Preferences implements _Preferences {
const _$_Preferences(
{required this.darkMode,
required this.themeColor,
{required this.themePreferences,
required this.language,
required this.displayScale,
required this.locking});
factory _$_Preferences.fromJson(Map<String, dynamic> json) =>
_$$_PreferencesFromJson(json);
@override
final DarkModePreference darkMode;
@override
final ColorPreference themeColor;
final ThemePreferences themePreferences;
@override
final LanguagePreference language;
@override
final int displayScale;
@override
final LockPreference locking;
@override
String toString() {
return 'Preferences(darkMode: $darkMode, themeColor: $themeColor, language: $language, displayScale: $displayScale, locking: $locking)';
return 'Preferences(themePreferences: $themePreferences, language: $language, locking: $locking)';
}
@override
@ -379,21 +543,17 @@ class _$_Preferences implements _Preferences {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_Preferences &&
(identical(other.darkMode, darkMode) ||
other.darkMode == darkMode) &&
(identical(other.themeColor, themeColor) ||
other.themeColor == themeColor) &&
(identical(other.themePreferences, themePreferences) ||
other.themePreferences == themePreferences) &&
(identical(other.language, language) ||
other.language == language) &&
(identical(other.displayScale, displayScale) ||
other.displayScale == displayScale) &&
(identical(other.locking, locking) || other.locking == locking));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, darkMode, themeColor, language, displayScale, locking);
int get hashCode =>
Object.hash(runtimeType, themePreferences, language, locking);
@JsonKey(ignore: true)
@override
@ -411,24 +571,18 @@ class _$_Preferences implements _Preferences {
abstract class _Preferences implements Preferences {
const factory _Preferences(
{required final DarkModePreference darkMode,
required final ColorPreference themeColor,
{required final ThemePreferences themePreferences,
required final LanguagePreference language,
required final int displayScale,
required final LockPreference locking}) = _$_Preferences;
factory _Preferences.fromJson(Map<String, dynamic> json) =
_$_Preferences.fromJson;
@override
DarkModePreference get darkMode;
@override
ColorPreference get themeColor;
ThemePreferences get themePreferences;
@override
LanguagePreference get language;
@override
int get displayScale;
@override
LockPreference get locking;
@override
@JsonKey(ignore: true)

View File

@ -20,20 +20,31 @@ Map<String, dynamic> _$$_LockPreferenceToJson(_$_LockPreference instance) =>
'lock_with_system_lock': instance.lockWithSystemLock,
};
_$_ThemePreferences _$$_ThemePreferencesFromJson(Map<String, dynamic> json) =>
_$_ThemePreferences(
brightnessPreference:
BrightnessPreference.fromJson(json['brightness_preference']),
colorPreference: ColorPreference.fromJson(json['color_preference']),
displayScale: (json['display_scale'] as num).toDouble(),
);
Map<String, dynamic> _$$_ThemePreferencesToJson(_$_ThemePreferences instance) =>
<String, dynamic>{
'brightness_preference': instance.brightnessPreference.toJson(),
'color_preference': instance.colorPreference.toJson(),
'display_scale': instance.displayScale,
};
_$_Preferences _$$_PreferencesFromJson(Map<String, dynamic> json) =>
_$_Preferences(
darkMode: DarkModePreference.fromJson(json['dark_mode']),
themeColor: ColorPreference.fromJson(json['theme_color']),
themePreferences: ThemePreferences.fromJson(json['theme_preferences']),
language: LanguagePreference.fromJson(json['language']),
displayScale: json['display_scale'] as int,
locking: LockPreference.fromJson(json['locking']),
);
Map<String, dynamic> _$$_PreferencesToJson(_$_Preferences instance) =>
<String, dynamic>{
'dark_mode': instance.darkMode.toJson(),
'theme_color': instance.themeColor.toJson(),
'theme_preferences': instance.themePreferences.toJson(),
'language': instance.language.toJson(),
'display_scale': instance.displayScale,
'locking': instance.locking.toJson(),
};

View File

@ -6,10 +6,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart';
import '../tools/desktop_control.dart';
import 'app.dart';
import 'log/log.dart';
import 'theming/theming.dart';
import 'providers/window_control.dart';
import 'tools/theme_service.dart';
import 'veilid_support/veilid_support.dart';
void main() async {
@ -32,7 +32,7 @@ void main() async {
final initTheme = themeService.initial;
// Manage window on desktop platforms
await setupDesktopWindow();
await WindowControl.initialize();
// Make localization delegate
final delegate = await LocalizationDelegate.create(

View File

@ -1,10 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:multi_split_view/multi_split_view.dart';
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
import '../components/chat.dart';
import '../components/chat_list.dart';
import '../providers/window_control.dart';
import '../tools/tools.dart';
import 'main_pager/main_pager.dart';
class HomePage extends ConsumerStatefulWidget {
const HomePage({super.key});
@ -17,6 +21,9 @@ class HomePage extends ConsumerStatefulWidget {
class HomePageState extends ConsumerState<HomePage>
with TickerProviderStateMixin {
final _unfocusNode = FocusNode();
final MultiSplitViewController _splitController = MultiSplitViewController(
areas: [Area(minimalSize: 300, weight: 0.25), Area(minimalSize: 300)]);
final scaffoldKey = GlobalKey<ScaffoldState>();
bool hasContainerTriggered = false;
final animationsMap = {
@ -38,12 +45,6 @@ class HomePageState extends ConsumerState<HomePage>
@override
void initState() {
super.initState();
// // On page load action.
// SchedulerBinding.instance.addPostFrameCallback((_) async {
// await actions.initialize(
// context,
// );
// });
setupAnimations(
animationsMap.values.where((anim) =>
@ -52,7 +53,11 @@ class HomePageState extends ConsumerState<HomePage>
this,
);
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
WidgetsBinding.instance.addPostFrameCallback((_) async {
setState(() {});
await ref.read(windowControlProvider.notifier).changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.normal);
});
}
@override
@ -61,250 +66,66 @@ class HomePageState extends ConsumerState<HomePage>
super.dispose();
}
// ignore: prefer_expression_function_bodies
Widget buildPhone(BuildContext context) {
//
return Material(
color: Colors.transparent, elevation: 4, child: MainPager());
}
// ignore: prefer_expression_function_bodies
Widget buildTabletLeftPane(BuildContext context) {
//
return Material(
color: Colors.transparent, elevation: 4, child: MainPager());
}
// ignore: prefer_expression_function_bodies
Widget buildTabletRightPane(BuildContext context) {
//
return Chat();
}
// ignore: prefer_expression_function_bodies
Widget buildTablet(BuildContext context) {
final children = [
buildTabletLeftPane(context),
buildTabletRightPane(context),
];
final multiSplitView = MultiSplitView(
// onWeightChange: _onWeightChange,
// onDividerTap: _onDividerTap,
// onDividerDoubleTap: _onDividerDoubleTap,
controller: _splitController,
children: children);
final theme = MultiSplitViewTheme(
data: isDesktop
? MultiSplitViewThemeData(
dividerThickness: 1,
dividerPainter: DividerPainters.grooved2(thickness: 1))
: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.grooved2(thickness: 1)),
child: multiSplitView);
return theme;
}
@override
Widget build(BuildContext context) => Scaffold(
key: scaffoldKey,
appBar: AppBar(title: const Text('VeilidChat')),
body: SafeArea(
Widget build(BuildContext context) {
ref.watch(windowControlProvider);
return SafeArea(
child: GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
child: Stack(
children: [
if (responsiveVisibility(
child: responsiveVisibility(
context: context,
phone: false,
))
Align(
alignment: AlignmentDirectional.centerEnd,
child: Container(
width: MediaQuery.of(context).size.width * 0.66,
height: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
),
child: Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(0),
),
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Padding(
padding:
const EdgeInsetsDirectional.fromSTEB(
16, 0, 16, 0),
child: Text(
"current contact",
// getJsonField(
// FFAppState().CurrentContact,
// r'''$.name''',
// ).toString(),
textAlign: TextAlign.start,
// style: Theme.of(context)
// .textTheme....
// .override(
// fontFamily: 'Noto Sans',
// color: FlutterFlowTheme.of(context)
// .header,
// ),
),
),
),
),
Expanded(
child: Container(
width: double.infinity,
height: 100,
decoration: const BoxDecoration(),
child: ChatComponentWidget(),
),
),
],
),
),
// if (FFAppState().CurrentContact == null)
// Container(
// width: double.infinity,
// height: double.infinity,
// decoration: const BoxDecoration(),
// child: NoContactComponentWidget(),
// ),
],
),
).animateOnActionTrigger(
animationsMap['containerOnActionTriggerAnimation']!,
hasBeenTriggered: hasContainerTriggered),
),
if (responsiveVisibility(
context: context,
phone: false,
))
Material(
color: Colors.transparent,
elevation: 4,
child: Container(
width: MediaQuery.of(context).size.width * 0.34,
height: double.infinity,
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.34,
),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
color: Theme.of(context).secondaryHeaderColor,
borderRadius: BorderRadius.circular(0),
),
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(
16, 8, 16, 8),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Text(
'Contacts',
textAlign: TextAlign.start,
// style: Theme.of(context).dialogTheme.titleTextStyle
// .title2
// .override(
// fontFamily: 'Noto Sans',
// color: FlutterFlowTheme.of(context)
// .header,
// ),
),
),
),
SignalStrengthIndicator.bars(
value: .5, //_signalStrength,
size: 50,
barCount: 5,
),
],
),
),
),
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
blurRadius: 0,
color: Color(0x33000000),
offset: Offset(0, 0),
)
],
),
child: ContactListComponentWidget(),
),
),
],
),
),
),
if (responsiveVisibility(
context: context,
tablet: false,
tabletLandscape: false,
desktop: false,
))
Material(
color: Colors.transparent,
elevation: 4,
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
//color: Theme.of(context).secondaryColor,
borderRadius: BorderRadius.circular(0),
),
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(
16, 8, 16, 8),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Align(
alignment:
const AlignmentDirectional(-1, 0),
child: Text(
'Contacts',
textAlign: TextAlign.start,
// style: FlutterFlowTheme.of(context)
// .title2
// .override(
// fontFamily: 'Noto Sans',
// color: FlutterFlowTheme.of(context)
// .header,
// ),
),
),
),
SignalStrengthIndicator.bars(
value: .5, //_signalStrength,
size: 50,
barCount: 5,
),
],
),
),
),
Expanded(
child: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
blurRadius: 0,
color: Color(0x33000000),
offset: Offset(0, 0),
)
],
),
child: ContactListComponentWidget(),
),
),
],
),
),
),
],
),
),
? buildTablet(context)
: buildPhone(context),
));
}
}

View File

@ -1,16 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:radix_colors/radix_colors.dart';
import '../tools/desktop_control.dart';
import '../providers/window_control.dart';
class IndexPage extends StatelessWidget {
class IndexPage extends ConsumerWidget {
const IndexPage({super.key});
static const path = '/';
@override
Widget build(BuildContext context) {
enableTitleBar(false);
Widget build(BuildContext context, WidgetRef ref) {
ref.watch(windowControlProvider);
return Scaffold(
body: DecoratedBox(
decoration: BoxDecoration(

View File

@ -0,0 +1,126 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:veilid/veilid.dart';
import '../../components/profile.dart';
import '../../entities/local_account.dart';
import '../../entities/proto.dart' as proto;
import '../../providers/account.dart';
import '../../providers/local_accounts.dart';
import '../../providers/logins.dart';
import '../../tools/tools.dart';
class AccountPage extends ConsumerStatefulWidget {
const AccountPage({super.key});
@override
AccountPageState createState() => AccountPageState();
}
class AccountPageState extends ConsumerState<AccountPage> {
final _unfocusNode = FocusNode();
TypedKey? _selectedAccount;
@override
void initState() {
super.initState();
_selectedAccount = null;
}
@override
void dispose() {
_unfocusNode.dispose();
super.dispose();
}
@override
// ignore: prefer_expression_function_bodies
Widget buildAccountList(BuildContext context) {
return Center(child: Text("account list"));
}
@override
Widget buildUnlockAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
// ignore: prefer_expression_function_bodies
) {
return Center(child: Text("unlock account"));
}
@override
Widget buildUserAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
TypedKey activeUserLogin,
proto.Account account,
// ignore: prefer_expression_function_bodies
) {
return ProfileWidget(
name: account.profile.name, title: account.profile.title);
}
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final localAccountsV = ref.watch(localAccountsProvider);
final loginsV = ref.watch(loginsProvider);
if (!localAccountsV.hasValue || !loginsV.hasValue) {
return waitingPage(context);
}
final localAccounts = localAccountsV.requireValue;
final logins = loginsV.requireValue;
final activeUserLogin = logins.activeUserLogin;
if (activeUserLogin == null) {
// If no logged in user is active, show the list of account
return buildAccountList(context);
}
final accountV = ref
.watch(fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
if (!accountV.hasValue) {
return waitingPage(context);
}
final account = accountV.requireValue;
switch (account.status) {
case AccountInfoStatus.noAccount:
Future.delayed(0.ms, () async {
await showErrorModal(
context,
translate('account_page.missing_account_title'),
translate('account_page.missing_account_text'));
// Delete account
await ref
.read(localAccountsProvider.notifier)
.deleteAccount(activeUserLogin);
});
return waitingPage(context);
case AccountInfoStatus.accountInvalid:
Future.delayed(0.ms, () async {
await showErrorModal(
context,
translate('account_page.invalid_account_title'),
translate('account_page.invalid_account_text'));
// Delete account
await ref
.read(localAccountsProvider.notifier)
.deleteAccount(activeUserLogin);
});
return waitingPage(context);
case AccountInfoStatus.accountLocked:
// Show unlock widget
return buildUnlockAccount(context, localAccounts);
case AccountInfoStatus.accountReady:
return buildUserAccount(
context,
localAccounts,
activeUserLogin,
account.account!,
);
}
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ChatsPage extends ConsumerStatefulWidget {
const ChatsPage({super.key});
@override
ChatsPageState createState() => ChatsPageState();
}
class ChatsPageState extends ConsumerState<ChatsPage> {
final _unfocusNode = FocusNode();
@override
void initState() {
super.initState();
}
@override
void dispose() {
_unfocusNode.dispose();
super.dispose();
}
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Center(child: Text("Conversations Page"));
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class ContactsPage extends ConsumerStatefulWidget {
const ContactsPage({super.key});
@override
ContactsPageState createState() => ContactsPageState();
}
class ContactsPageState extends ConsumerState<ContactsPage> {
final _unfocusNode = FocusNode();
@override
void initState() {
super.initState();
}
@override
void dispose() {
_unfocusNode.dispose();
super.dispose();
}
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Center(child: Text("Contacts Page"));
}
}

View File

@ -0,0 +1,180 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:stylish_bottom_bar/model/bar_items.dart';
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
import 'account_page.dart';
import 'contacts_page.dart';
import 'chats_page.dart';
class MainPager extends ConsumerStatefulWidget {
const MainPager({super.key});
@override
MainPagerState createState() => MainPagerState();
}
class MainPagerState extends ConsumerState<MainPager>
with TickerProviderStateMixin {
//////////////////////////////////////////////////////////////////
final _unfocusNode = FocusNode();
final _pageController = PageController();
var _currentPage = 0;
final _selectedIconList = <IconData>[
Icons.chat,
Icons.contacts,
Icons.person
];
// final _unselectedIconList = <IconData>[
// Icons.chat_outlined,
// Icons.contacts_outlined,
// Icons.person_outlined
// ];
final _fabIconList = <IconData>[
Icons.add_comment_sharp,
Icons.person_add_sharp,
Icons.settings_sharp,
];
final _labelList = <String>[
translate('pager.chats'),
translate('pager.contacts'),
translate('pager.account'),
];
final List<Widget> _bottomBarPages = [
const ChatsPage(),
const ContactsPage(),
const AccountPage(),
];
//////////////////////////////////////////////////////////////////
@override
void initState() {
super.initState();
}
@override
void dispose() {
_unfocusNode.dispose();
_pageController.dispose();
super.dispose();
}
bool onScrollNotification(ScrollNotification notification) {
if (notification is UserScrollNotification &&
notification.metrics.axis == Axis.vertical) {
switch (notification.direction) {
case ScrollDirection.forward:
// _hideBottomBarAnimationController.reverse();
// _fabAnimationController.forward(from: 0);
break;
case ScrollDirection.reverse:
// _hideBottomBarAnimationController.forward();
// _fabAnimationController.reverse(from: 1);
break;
case ScrollDirection.idle:
break;
}
}
return false;
}
BottomBarItem buildBottomBarItem(int index) {
final theme = Theme.of(context);
return BottomBarItem(
title: Text(_labelList[index]),
icon: Icon(_selectedIconList[index],
color: theme.colorScheme.onPrimaryContainer),
selectedIcon: Icon(_selectedIconList[index],
color: theme.colorScheme.onPrimaryContainer),
backgroundColor: theme.colorScheme.onPrimaryContainer,
//unSelectedColor: theme.colorScheme.primaryContainer,
//selectedColor: theme.colorScheme.primary,
//badge: const Text('9+'),
//showBadge: true,
);
}
List<BottomBarItem> _buildBottomBarItems() {
final bottomBarItems = List<BottomBarItem>.empty(growable: true);
for (var index = 0; index < _bottomBarPages.length; index++) {
final item = buildBottomBarItem(index);
bottomBarItems.add(item);
}
return bottomBarItems;
}
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
extendBody: true,
body: NotificationListener<ScrollNotification>(
onNotification: onScrollNotification,
child: PageView(
controller: _pageController,
//physics: const NeverScrollableScrollPhysics(),
children: List.generate(
_bottomBarPages.length, (index) => _bottomBarPages[index]),
)),
// appBar: AppBar(
// toolbarHeight: 24,
// title: Text(
// 'C',
// style: Theme.of(context).textTheme.headlineSmall,
// ),
// ),
bottomNavigationBar: StylishBottomBar(
backgroundColor: theme.colorScheme.primaryContainer,
// gradient: LinearGradient(
// begin: Alignment.topCenter,
// end: Alignment.bottomCenter,
// colors: <Color>[
// theme.colorScheme.primary,
// theme.colorScheme.primaryContainer,
// ]),
option: AnimatedBarOptions(
// iconSize: 32,
//barAnimation: BarAnimation.fade,
iconStyle: IconStyle.animated,
inkEffect: true,
inkColor: theme.colorScheme.primary,
//opacity: 0.3,
),
items: _buildBottomBarItems(),
hasNotch: true,
fabLocation: StylishBarFabLocation.end,
currentIndex: _currentPage,
onTap: (index) async {
await _pageController.animateToPage(index,
duration: 250.ms, curve: Curves.bounceOut);
setState(() {
_currentPage = index;
});
},
),
floatingActionButton: FloatingActionButton(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14))),
//foregroundColor: theme.colorScheme.secondary,
backgroundColor: theme.colorScheme.secondaryContainer,
child: Icon(
_fabIconList[_currentPage],
color: theme.colorScheme.onSecondaryContainer,
),
onPressed: () {
// xxx
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
);
}
}

View File

@ -11,6 +11,7 @@ import '../components/default_app_bar.dart';
import '../entities/proto.dart' as proto;
import '../providers/local_accounts.dart';
import '../providers/logins.dart';
import '../providers/window_control.dart';
import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart';
@ -111,8 +112,7 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
@override
Widget build(BuildContext context) {
enableTitleBar(true);
portraitOnly();
ref.watch(windowControlProvider);
final localAccounts = ref.watch(localAccountsProvider);
final logins = ref.watch(loginsProvider);
@ -132,15 +132,8 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
try {
await createAccount();
} on Exception catch (e) {
await QuickAlert.show(
context: context,
type: QuickAlertType.error,
title: translate('new_account_page.error'),
text: 'Exception: $e',
//backgroundColor: Colors.black,
//titleColor: Colors.white,
//textColor: Colors.white,
);
await showErrorModal(
context, translate('new_account_page.error'), 'Exception: $e');
}
},
).paddingSymmetric(horizontal: 24, vertical: 8),

112
lib/providers/account.dart Normal file
View File

@ -0,0 +1,112 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilid/veilid.dart';
import '../entities/entities.dart';
import '../entities/proto.dart' as proto;
import '../veilid_support/veilid_support.dart';
import 'local_accounts.dart';
import 'logins.dart';
part 'account.g.dart';
enum AccountInfoStatus {
noAccount,
accountInvalid,
accountLocked,
accountReady,
}
class AccountInfo {
AccountInfo({
required this.status,
required this.active,
this.account,
});
AccountInfoStatus status;
bool active;
proto.Account? account;
}
@riverpod
Future<AccountInfo> fetchAccount(FetchAccountRef ref,
{required TypedKey accountMasterRecordKey}) async {
// Get which local account we want to fetch the profile for
final veilid = await eventualVeilid.future;
final localAccount = await ref.watch(
fetchLocalAccountProvider(accountMasterRecordKey: accountMasterRecordKey)
.future);
if (localAccount == null) {
// Local account does not exist
return AccountInfo(status: AccountInfoStatus.noAccount, active: false);
}
// See if we've logged into this account or if it is locked
final activeUserLogin = await ref.watch(loginsProvider.future
.select((value) async => (await value).activeUserLogin));
final active = activeUserLogin == accountMasterRecordKey;
final login = await ref.watch(
fetchLoginProvider(accountMasterRecordKey: accountMasterRecordKey)
.future);
if (login == null) {
// Account was locked
return AccountInfo(status: AccountInfoStatus.accountLocked, active: active);
}
// Read the identity key to get the account keys
final dhtctx = (await veilid.routingContext())
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
final identityRecordCrypto = await DHTRecordCryptoPrivate.fromSecret(
localAccount.identityMaster.identityRecordKey.kind,
login.identitySecret.value);
late final TypedKey accountRecordKey;
late final KeyPair accountRecordOwner;
await (await DHTRecord.openRead(
dhtctx, localAccount.identityMaster.identityRecordKey,
crypto: identityRecordCrypto))
.scope((identityRec) async {
final identity = await identityRec.getJson(Identity.fromJson);
if (identity == null) {
// Identity could not be read or decrypted from DHT
return AccountInfo(
status: AccountInfoStatus.accountInvalid, active: active);
}
final accountRecords = IMapOfSets.from(identity.accountRecords);
final vcAccounts = accountRecords.get(veilidChatAccountKey);
if (vcAccounts.length != 1) {
// No veilidchat account, or multiple accounts
// somehow associated with identity
return AccountInfo(
status: AccountInfoStatus.accountInvalid, active: active);
}
final accountRecordInfo = vcAccounts.first;
accountRecordKey = accountRecordInfo.key;
accountRecordOwner = accountRecordInfo.owner;
});
// Pull the account DHT key, decode it and return it
final accountRecordCrypto = await DHTRecordCryptoPrivate.fromSecret(
accountRecordKey.kind, accountRecordOwner.secret);
late final proto.Account account;
await (await DHTRecord.openRead(dhtctx, accountRecordKey,
crypto: accountRecordCrypto))
.scope((accountRec) async {
final protoAccount = await accountRec.getProtobuf(proto.Account.fromBuffer);
if (protoAccount == null) {
// Account could not be read or decrypted from DHT
return AccountInfo(
status: AccountInfoStatus.accountInvalid, active: active);
}
account = protoAccount;
});
// Got account, decrypted and decoded
return AccountInfo(
status: AccountInfoStatus.accountReady, active: active, account: account);
}

View File

@ -0,0 +1,113 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$fetchAccountHash() => r'4d94703d07a21509650e19f60ea67ac96a39742e';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef FetchAccountRef = AutoDisposeFutureProviderRef<AccountInfo>;
/// See also [fetchAccount].
@ProviderFor(fetchAccount)
const fetchAccountProvider = FetchAccountFamily();
/// See also [fetchAccount].
class FetchAccountFamily extends Family<AsyncValue<AccountInfo>> {
/// See also [fetchAccount].
const FetchAccountFamily();
/// See also [fetchAccount].
FetchAccountProvider call({
required Typed<FixedEncodedString43> accountMasterRecordKey,
}) {
return FetchAccountProvider(
accountMasterRecordKey: accountMasterRecordKey,
);
}
@override
FetchAccountProvider getProviderOverride(
covariant FetchAccountProvider provider,
) {
return call(
accountMasterRecordKey: provider.accountMasterRecordKey,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'fetchAccountProvider';
}
/// See also [fetchAccount].
class FetchAccountProvider extends AutoDisposeFutureProvider<AccountInfo> {
/// See also [fetchAccount].
FetchAccountProvider({
required this.accountMasterRecordKey,
}) : super.internal(
(ref) => fetchAccount(
ref,
accountMasterRecordKey: accountMasterRecordKey,
),
from: fetchAccountProvider,
name: r'fetchAccountProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$fetchAccountHash,
dependencies: FetchAccountFamily._dependencies,
allTransitiveDependencies:
FetchAccountFamily._allTransitiveDependencies,
);
final Typed<FixedEncodedString43> accountMasterRecordKey;
@override
bool operator ==(Object other) {
return other is FetchAccountProvider &&
other.accountMasterRecordKey == accountMasterRecordKey;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, accountMasterRecordKey.hashCode);
return _SystemHash.finish(hash);
}
}
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View File

@ -14,6 +14,8 @@ import 'logins.dart';
part 'local_accounts.g.dart';
const String veilidChatAccountKey = 'com.veilid.veilidchat';
// Local account manager
@riverpod
class LocalAccounts extends _$LocalAccounts
@ -90,6 +92,7 @@ class LocalAccounts extends _$LocalAccounts
encryptionKeyType: encryptionKeyType,
biometricsEnabled: false,
hiddenAccount: false,
name: account.profile.name,
);
/////// Add account with profile to DHT
@ -114,8 +117,14 @@ class LocalAccounts extends _$LocalAccounts
await identityRec.eventualUpdateJson(Identity.fromJson,
(oldIdentity) async {
final accountRecords = IMapOfSets.from(oldIdentity.accountRecords)
.add('com.veilid.veilidchat', newAccountRecordInfo)
final oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords);
// Only allow one account per identity for veilidchat
if (oldAccountRecords.get(veilidChatAccountKey).isNotEmpty) {
throw StateError(
'Only one account per identity allowed for VeilidChat');
}
final accountRecords = oldAccountRecords
.add(veilidChatAccountKey, newAccountRecordInfo)
.asIMap();
return oldIdentity.copyWith(accountRecords: accountRecords);
});
@ -151,3 +160,18 @@ class LocalAccounts extends _$LocalAccounts
/// Recover an account with the master identity secret
}
@riverpod
Future<LocalAccount?> fetchLocalAccount(FetchLocalAccountRef ref,
{required TypedKey accountMasterRecordKey}) async {
final localAccounts = await ref.watch(localAccountsProvider.future);
try {
return localAccounts.firstWhere(
(e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
} on Exception catch (e) {
if (e is StateError) {
return null;
}
rethrow;
}
}

View File

@ -6,7 +6,113 @@ part of 'local_accounts.dart';
// RiverpodGenerator
// **************************************************************************
String _$localAccountsHash() => r'f69de269e15fabb83d1afbb7fdb6eb7693d0ce24';
String _$fetchLocalAccountHash() => r'e9f8ea0dd15031cc8145532e9cac73ab7f0f81be';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef FetchLocalAccountRef = AutoDisposeFutureProviderRef<LocalAccount?>;
/// See also [fetchLocalAccount].
@ProviderFor(fetchLocalAccount)
const fetchLocalAccountProvider = FetchLocalAccountFamily();
/// See also [fetchLocalAccount].
class FetchLocalAccountFamily extends Family<AsyncValue<LocalAccount?>> {
/// See also [fetchLocalAccount].
const FetchLocalAccountFamily();
/// See also [fetchLocalAccount].
FetchLocalAccountProvider call({
required Typed<FixedEncodedString43> accountMasterRecordKey,
}) {
return FetchLocalAccountProvider(
accountMasterRecordKey: accountMasterRecordKey,
);
}
@override
FetchLocalAccountProvider getProviderOverride(
covariant FetchLocalAccountProvider provider,
) {
return call(
accountMasterRecordKey: provider.accountMasterRecordKey,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'fetchLocalAccountProvider';
}
/// See also [fetchLocalAccount].
class FetchLocalAccountProvider
extends AutoDisposeFutureProvider<LocalAccount?> {
/// See also [fetchLocalAccount].
FetchLocalAccountProvider({
required this.accountMasterRecordKey,
}) : super.internal(
(ref) => fetchLocalAccount(
ref,
accountMasterRecordKey: accountMasterRecordKey,
),
from: fetchLocalAccountProvider,
name: r'fetchLocalAccountProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$fetchLocalAccountHash,
dependencies: FetchLocalAccountFamily._dependencies,
allTransitiveDependencies:
FetchLocalAccountFamily._allTransitiveDependencies,
);
final Typed<FixedEncodedString43> accountMasterRecordKey;
@override
bool operator ==(Object other) {
return other is FetchLocalAccountProvider &&
other.accountMasterRecordKey == accountMasterRecordKey;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, accountMasterRecordKey.hashCode);
return _SystemHash.finish(hash);
}
}
String _$localAccountsHash() => r'c8214abbc9720449910e74e32fc52d53ca4453c0';
/// See also [LocalAccounts].
@ProviderFor(LocalAccounts)

View File

@ -163,3 +163,18 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
state = AsyncValue.data(updated);
}
}
@riverpod
Future<UserLogin?> fetchLogin(FetchLoginRef ref,
{required TypedKey accountMasterRecordKey}) async {
final activeLogins = await ref.watch(loginsProvider.future);
try {
return activeLogins.userLogins
.firstWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey);
} on Exception catch (e) {
if (e is StateError) {
return null;
}
rethrow;
}
}

View File

@ -6,6 +6,111 @@ part of 'logins.dart';
// RiverpodGenerator
// **************************************************************************
String _$fetchLoginHash() => r'cfe13f5152f1275e6eccc698142abfd98170d9b9';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
typedef FetchLoginRef = AutoDisposeFutureProviderRef<UserLogin?>;
/// See also [fetchLogin].
@ProviderFor(fetchLogin)
const fetchLoginProvider = FetchLoginFamily();
/// See also [fetchLogin].
class FetchLoginFamily extends Family<AsyncValue<UserLogin?>> {
/// See also [fetchLogin].
const FetchLoginFamily();
/// See also [fetchLogin].
FetchLoginProvider call({
required Typed<FixedEncodedString43> accountMasterRecordKey,
}) {
return FetchLoginProvider(
accountMasterRecordKey: accountMasterRecordKey,
);
}
@override
FetchLoginProvider getProviderOverride(
covariant FetchLoginProvider provider,
) {
return call(
accountMasterRecordKey: provider.accountMasterRecordKey,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'fetchLoginProvider';
}
/// See also [fetchLogin].
class FetchLoginProvider extends AutoDisposeFutureProvider<UserLogin?> {
/// See also [fetchLogin].
FetchLoginProvider({
required this.accountMasterRecordKey,
}) : super.internal(
(ref) => fetchLogin(
ref,
accountMasterRecordKey: accountMasterRecordKey,
),
from: fetchLoginProvider,
name: r'fetchLoginProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$fetchLoginHash,
dependencies: FetchLoginFamily._dependencies,
allTransitiveDependencies:
FetchLoginFamily._allTransitiveDependencies,
);
final Typed<FixedEncodedString43> accountMasterRecordKey;
@override
bool operator ==(Object other) {
return other is FetchLoginProvider &&
other.accountMasterRecordKey == accountMasterRecordKey;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, accountMasterRecordKey.hashCode);
return _SystemHash.finish(hash);
}
}
String _$loginsHash() => r'ed9dbe91a248f662ccb0fac6edf5b1892cf2ef92';
/// See also [Logins].

View File

@ -1,3 +1,5 @@
export 'account_profile.dart';
export 'connection_state.dart';
export 'local_accounts.dart';
export 'logins.dart';
export 'window_control.dart';

View File

@ -0,0 +1,83 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:window_manager/window_manager.dart';
import '../tools/responsive.dart';
export 'package:window_manager/window_manager.dart' show TitleBarStyle;
part 'window_control.g.dart';
enum OrientationCapability {
normal,
portraitOnly,
landscapeOnly,
}
// Local account manager
@riverpod
class WindowControl extends _$WindowControl {
/// Change window control
@override
FutureOr<bool> build() async {
await _doWindowSetup(TitleBarStyle.hidden, OrientationCapability.normal);
return true;
}
static Future<void> initialize() async {
if (isDesktop) {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(768, 1024),
//minimumSize: Size(480, 640),
center: true,
backgroundColor: Colors.transparent,
skipTaskbar: false,
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
}
}
Future<void> _doWindowSetup(TitleBarStyle titleBarStyle,
OrientationCapability orientationCapability) async {
if (isDesktop) {
await windowManager.setTitleBarStyle(titleBarStyle);
} else {
switch (orientationCapability) {
case OrientationCapability.normal:
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
case OrientationCapability.portraitOnly:
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
case OrientationCapability.landscapeOnly:
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
}
}
//////////////////////////////////////////////////////////////
/// Mutators and Selectors
/// Reorder accounts
Future<void> changeWindowSetup(TitleBarStyle titleBarStyle,
OrientationCapability orientationCapability) async {
state = const AsyncValue.loading();
await _doWindowSetup(titleBarStyle, orientationCapability);
state = const AsyncValue.data(true);
}
}

View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'window_control.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$windowControlHash() => r'c6afcbe1d4bfcfc580c30393aac60624c5ceabe0';
/// See also [WindowControl].
@ProviderFor(WindowControl)
final windowControlProvider =
AutoDisposeAsyncNotifierProvider<WindowControl, bool>.internal(
WindowControl.new,
name: r'windowControlProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$windowControlHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$WindowControl = AutoDisposeAsyncNotifier<bool>;
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View File

@ -6,7 +6,7 @@ part of 'router_notifier.dart';
// RiverpodGenerator
// **************************************************************************
String _$routerNotifierHash() => r'ef31219dde5e12b2bb224c79ca13ab4f414c81b4';
String _$routerNotifierHash() => r'8e7b9debfa144253e25871edf920bf315f28a861';
/// See also [RouterNotifier].
@ProviderFor(RouterNotifier)

View File

@ -1,54 +0,0 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'themes/themes.dart';
class ThemeService {
ThemeService._();
static late SharedPreferences prefs;
static ThemeService? _instance;
static Future<ThemeService> get instance async {
if (_instance == null) {
prefs = await SharedPreferences.getInstance();
_instance = ThemeService._();
}
return _instance!;
}
final allThemes = <String, ThemeData>{
'dark': darkTheme,
'light': lightTheme,
};
String get previousThemeName {
var themeName = prefs.getString('previousThemeName');
if (themeName == null) {
final isPlatformDark =
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.dark;
themeName = isPlatformDark ? 'dark' : 'light';
}
return themeName;
}
ThemeData get initial {
var themeName = prefs.getString('theme');
if (themeName == null) {
final isPlatformDark =
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.dark;
themeName = isPlatformDark ? 'dark' : 'light';
}
return allThemes[themeName] ?? allThemes['light']!;
}
Future<void> save(String newThemeName) async {
final currentThemeName = prefs.getString('theme');
if (currentThemeName != null) {
await prefs.setString('previousThemeName', currentThemeName);
}
await prefs.setString('theme', newThemeName);
}
ThemeData getByName(String name) => allThemes[name]!;
}

View File

@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
ThemeData darkTheme = ThemeData.dark();
// late Color primaryColor = const Color(0xFF343455);
// late Color secondaryColor = const Color(0xFF3D3E77);
// late Color tertiaryColor = const Color(0xFFBB108E);
// late Color alternate = const Color(0xFF5086DF);
// late Color primaryBackground = const Color(0xFF252534);
// late Color secondaryBackground = const Color(0xFF292D44);
// late Color primaryText = const Color(0xFFD0D0E0);
// late Color secondaryText = const Color(0xFFB0B0D0);
// late Color disabledText = Color(0x808F8F8F);
// late Color primaryEdge = Color(0xFF555594);
// late Color header = Color(0xFF8A8AD8);
// late Color textBackground = Color(0xFF181820);
// late Color active = Color(0xFF463BAD);
// late Color inactive = Color(0xFF2E2E3C);

View File

@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
ThemeData lightTheme = ThemeData.light();
// late Color primaryColor = const Color(0xFF6667AB);
// late Color secondaryColor = const Color(0xFF7C7ED0);
// late Color tertiaryColor = const Color(0xFFF259C9);
// late Color alternate = const Color(0xFF77ABFF);
// late Color primaryBackground = const Color(0xFFA0A0D0);
// late Color secondaryBackground = const Color(0xFFB0B0D0);
// late Color primaryText = const Color(0xFF101010);
// late Color secondaryText = const Color(0xFF282840);
// late Color disabledText = Color(0x8047464F);
// late Color primaryEdge = Color(0xFF44447F);
// late Color header = Color(0xFFE0E0F0);
// late Color textBackground = Color(0xFFE0E0F0);
// late Color active = Color(0xFF5B4FFA);
// late Color inactive = Color(0xFF3E3E72);

View File

@ -1,2 +0,0 @@
export 'dark.dart';
export 'light.dart';

View File

@ -1,2 +0,0 @@
export 'theme_service.dart';
export 'themes/themes.dart';

View File

@ -1,48 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:window_manager/window_manager.dart';
Future<void> setupDesktopWindow() async {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(768, 1024),
minimumSize: Size(480, 640),
center: true,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
}
}
Future<void> enableTitleBar(bool enabled) async {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
if (enabled) {
await windowManager.setTitleBarStyle(TitleBarStyle.normal);
} else {
await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
}
}
}
Future<void> portraitOnly() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
Future<void> landscapeOnly() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}

View File

@ -0,0 +1,445 @@
import 'package:flutter/material.dart';
import 'package:radix_colors/radix_colors.dart';
enum RadixThemeColor {
scarlet, // tomato + red + violet
babydoll, // crimson + purple + pink
vapor, // pink + cyan + plum
gold, // yellow + amber + orange
garden, // grass + orange + brown
forest, // green + brown + amber
arctic, // sky + teal + violet
lapis, // blue + indigo + mint
eggplant, // violet + purple + indigo
lime, // lime + yellow + orange
grim, // mauve + slate + sage
}
enum _RadixBaseColor {
tomato,
red,
crimson,
pink,
plum,
purple,
violet,
indigo,
blue,
sky,
cyan,
teal,
mint,
green,
grass,
lime,
yellow,
amber,
orange,
brown,
}
RadixColor _radixGraySteps(
Brightness brightness, bool alpha, _RadixBaseColor baseColor) {
switch (baseColor) {
case _RadixBaseColor.tomato:
case _RadixBaseColor.red:
case _RadixBaseColor.crimson:
case _RadixBaseColor.pink:
case _RadixBaseColor.plum:
case _RadixBaseColor.purple:
case _RadixBaseColor.violet:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.mauveA
: RadixColors.mauveA)
: (brightness == Brightness.dark
? RadixColors.dark.mauve
: RadixColors.mauve);
case _RadixBaseColor.indigo:
case _RadixBaseColor.blue:
case _RadixBaseColor.sky:
case _RadixBaseColor.cyan:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.slateA
: RadixColors.slateA)
: (brightness == Brightness.dark
? RadixColors.dark.slate
: RadixColors.slate);
case _RadixBaseColor.teal:
case _RadixBaseColor.mint:
case _RadixBaseColor.green:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.sageA
: RadixColors.sageA)
: (brightness == Brightness.dark
? RadixColors.dark.sage
: RadixColors.sage);
case _RadixBaseColor.lime:
case _RadixBaseColor.grass:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.oliveA
: RadixColors.oliveA)
: (brightness == Brightness.dark
? RadixColors.dark.olive
: RadixColors.olive);
case _RadixBaseColor.yellow:
case _RadixBaseColor.amber:
case _RadixBaseColor.orange:
case _RadixBaseColor.brown:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.sandA
: RadixColors.sandA)
: (brightness == Brightness.dark
? RadixColors.dark.sand
: RadixColors.sand);
}
}
RadixColor _radixColorSteps(
Brightness brightness, bool alpha, _RadixBaseColor baseColor) {
switch (baseColor) {
case _RadixBaseColor.tomato:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.tomatoA
: RadixColors.tomatoA)
: (brightness == Brightness.dark
? RadixColors.dark.tomato
: RadixColors.tomato);
case _RadixBaseColor.red:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.redA
: RadixColors.redA)
: (brightness == Brightness.dark
? RadixColors.dark.red
: RadixColors.red);
case _RadixBaseColor.crimson:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.crimsonA
: RadixColors.crimsonA)
: (brightness == Brightness.dark
? RadixColors.dark.crimson
: RadixColors.crimson);
case _RadixBaseColor.pink:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.pinkA
: RadixColors.pinkA)
: (brightness == Brightness.dark
? RadixColors.dark.pink
: RadixColors.pink);
case _RadixBaseColor.plum:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.plumA
: RadixColors.plumA)
: (brightness == Brightness.dark
? RadixColors.dark.plum
: RadixColors.plum);
case _RadixBaseColor.purple:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.purpleA
: RadixColors.purpleA)
: (brightness == Brightness.dark
? RadixColors.dark.purple
: RadixColors.purple);
case _RadixBaseColor.violet:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.violetA
: RadixColors.violetA)
: (brightness == Brightness.dark
? RadixColors.dark.violet
: RadixColors.violet);
case _RadixBaseColor.indigo:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.indigoA
: RadixColors.indigoA)
: (brightness == Brightness.dark
? RadixColors.dark.indigo
: RadixColors.indigo);
case _RadixBaseColor.blue:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.blueA
: RadixColors.blueA)
: (brightness == Brightness.dark
? RadixColors.dark.blue
: RadixColors.blue);
case _RadixBaseColor.sky:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.skyA
: RadixColors.skyA)
: (brightness == Brightness.dark
? RadixColors.dark.sky
: RadixColors.sky);
case _RadixBaseColor.cyan:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.cyanA
: RadixColors.cyanA)
: (brightness == Brightness.dark
? RadixColors.dark.cyan
: RadixColors.cyan);
case _RadixBaseColor.teal:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.tealA
: RadixColors.tealA)
: (brightness == Brightness.dark
? RadixColors.dark.teal
: RadixColors.teal);
case _RadixBaseColor.mint:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.mintA
: RadixColors.mintA)
: (brightness == Brightness.dark
? RadixColors.dark.mint
: RadixColors.mint);
case _RadixBaseColor.green:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.greenA
: RadixColors.greenA)
: (brightness == Brightness.dark
? RadixColors.dark.green
: RadixColors.green);
case _RadixBaseColor.grass:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.grassA
: RadixColors.grassA)
: (brightness == Brightness.dark
? RadixColors.dark.grass
: RadixColors.grass);
case _RadixBaseColor.lime:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.limeA
: RadixColors.limeA)
: (brightness == Brightness.dark
? RadixColors.dark.lime
: RadixColors.lime);
case _RadixBaseColor.yellow:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.yellowA
: RadixColors.yellowA)
: (brightness == Brightness.dark
? RadixColors.dark.yellow
: RadixColors.yellow);
case _RadixBaseColor.amber:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.amberA
: RadixColors.amberA)
: (brightness == Brightness.dark
? RadixColors.dark.amber
: RadixColors.amber);
case _RadixBaseColor.orange:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.orangeA
: RadixColors.orangeA)
: (brightness == Brightness.dark
? RadixColors.dark.orange
: RadixColors.orange);
case _RadixBaseColor.brown:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.brownA
: RadixColors.brownA)
: (brightness == Brightness.dark
? RadixColors.dark.brown
: RadixColors.brown);
}
}
ColorScheme _radixColorScheme(
// ignore: prefer_expression_function_bodies
Brightness brightness,
RadixThemeColor themeColor) {
late RadixColor primaryScale;
late RadixColor primaryAlphaScale;
late RadixColor secondaryScale;
late RadixColor tertiaryScale;
late RadixColor grayScale;
late RadixColor errorScale;
switch (themeColor) {
// tomato + red + violet
case RadixThemeColor.scarlet:
primaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.tomato);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.tomato);
secondaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.red);
tertiaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.violet);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.tomato);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.yellow);
// crimson + purple + pink
case RadixThemeColor.babydoll:
primaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.crimson);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.crimson);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.purple);
tertiaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.pink);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.crimson);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.orange);
// pink + cyan + plum
case RadixThemeColor.vapor:
primaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.pink);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.pink);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.cyan);
tertiaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.plum);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.pink);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.red);
// yellow + amber + orange
case RadixThemeColor.gold:
primaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.yellow);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.yellow);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.amber);
tertiaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.orange);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.yellow);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.red);
// grass + orange + brown
case RadixThemeColor.garden:
primaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.grass);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.grass);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.orange);
tertiaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.brown);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.grass);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.tomato);
// green + brown + amber
case RadixThemeColor.forest:
primaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.green);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.green);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.brown);
tertiaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.amber);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.green);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.tomato);
// sky + teal + violet
case RadixThemeColor.arctic:
primaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.sky);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.sky);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.teal);
tertiaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.violet);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.sky);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.crimson);
// blue + indigo + mint
case RadixThemeColor.lapis:
primaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.blue);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.blue);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.indigo);
tertiaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.mint);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.blue);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.crimson);
// violet + purple + indigo
case RadixThemeColor.eggplant:
primaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.violet);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.violet);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.purple);
tertiaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.indigo);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.violet);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.crimson);
// lime + yellow + orange
case RadixThemeColor.lime:
primaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.lime);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.lime);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.yellow);
tertiaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.orange);
grayScale = _radixGraySteps(brightness, false, _RadixBaseColor.lime);
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.red);
// mauve + slate + sage
case RadixThemeColor.grim:
primaryScale = _radixGraySteps(brightness, false, _RadixBaseColor.tomato);
primaryAlphaScale =
_radixColorSteps(brightness, true, _RadixBaseColor.tomato);
secondaryScale =
_radixColorSteps(brightness, false, _RadixBaseColor.indigo);
tertiaryScale = _radixColorSteps(brightness, false, _RadixBaseColor.teal);
grayScale = brightness == Brightness.dark
? RadixColors.dark.gray
: RadixColors.gray;
errorScale = _radixColorSteps(brightness, false, _RadixBaseColor.red);
}
return ColorScheme(
brightness: brightness,
primary: primaryScale.step9,
onPrimary: primaryScale.step12,
primaryContainer: primaryScale.step3,
onPrimaryContainer: primaryScale.step11,
secondary: secondaryScale.step9,
onSecondary: secondaryScale.step12,
secondaryContainer: secondaryScale.step3,
onSecondaryContainer: secondaryScale.step11,
tertiary: tertiaryScale.step9,
onTertiary: tertiaryScale.step12,
tertiaryContainer: tertiaryScale.step3,
onTertiaryContainer: tertiaryScale.step11,
error: errorScale.step9,
onError: errorScale.step12,
errorContainer: errorScale.step3,
onErrorContainer: errorScale.step11,
background: primaryScale.step1, //gray scale?
onBackground: primaryScale.step12,
surface: primaryScale.step2, //gray scale?
onSurface: primaryScale.step11,
surfaceVariant: primaryScale.step3, //gray scale?
onSurfaceVariant: primaryScale.step11,
outline: primaryScale.step7,
outlineVariant: primaryScale.step6,
shadow: RadixColors.dark.gray.step1,
scrim: primaryScale.step4,
inverseSurface: primaryScale.step11,
onInverseSurface: primaryScale.step2,
inversePrimary: primaryScale.step10,
surfaceTint: primaryAlphaScale.step9,
);
}
ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
TextTheme? textTheme;
return ThemeData.from(
colorScheme: _radixColorScheme(brightness, themeColor),
textTheme: textTheme,
useMaterial3: true);
}

View File

@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
bool get isAndroid => !kIsWeb && Platform.isAndroid;
bool get isiOS => !kIsWeb && Platform.isIOS;
bool get isWeb => kIsWeb;
bool get isDesktop =>
Platform.isWindows || Platform.isLinux || Platform.isMacOS;
const kMobileWidthCutoff = 479.0;

View File

@ -0,0 +1,91 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../entities/preferences.dart';
import 'radix_generator.dart';
class ThemeService {
ThemeService._();
static late SharedPreferences prefs;
static ThemeService? _instance;
static Future<ThemeService> get instance async {
if (_instance == null) {
prefs = await SharedPreferences.getInstance();
_instance = ThemeService._();
}
return _instance!;
}
static bool get isPlatformDark =>
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.dark;
ThemeData get initial {
final themePreferencesJson = prefs.getString('themePreferences');
final themePreferences = themePreferencesJson != null
? ThemePreferences.fromJson(themePreferencesJson)
: const ThemePreferences(
colorPreference: ColorPreference.vapor,
brightnessPreference: BrightnessPreference.system,
displayScale: 1,
);
return get(themePreferences);
}
Future<void> save(ThemePreferences themePreferences) async {
await prefs.setString(
'themePreferences', jsonEncode(themePreferences.toJson()));
}
ThemeData get(ThemePreferences themePreferences) {
late final Brightness brightness;
switch (themePreferences.brightnessPreference) {
case BrightnessPreference.system:
if (isPlatformDark) {
brightness = Brightness.dark;
} else {
brightness = Brightness.light;
}
case BrightnessPreference.light:
brightness = Brightness.light;
case BrightnessPreference.dark:
brightness = Brightness.dark;
}
late final ThemeData themeData;
switch (themePreferences.colorPreference) {
// Special cases
case ColorPreference.contrast:
// xxx do contrastGenerator
themeData = radixGenerator(brightness, RadixThemeColor.grim);
// Generate from Radix
case ColorPreference.scarlet:
themeData = radixGenerator(brightness, RadixThemeColor.scarlet);
case ColorPreference.babydoll:
themeData = radixGenerator(brightness, RadixThemeColor.babydoll);
case ColorPreference.vapor:
themeData = radixGenerator(brightness, RadixThemeColor.vapor);
case ColorPreference.gold:
themeData = radixGenerator(brightness, RadixThemeColor.gold);
case ColorPreference.garden:
themeData = radixGenerator(brightness, RadixThemeColor.garden);
case ColorPreference.forest:
themeData = radixGenerator(brightness, RadixThemeColor.forest);
case ColorPreference.arctic:
themeData = radixGenerator(brightness, RadixThemeColor.arctic);
case ColorPreference.lapis:
themeData = radixGenerator(brightness, RadixThemeColor.lapis);
case ColorPreference.eggplant:
themeData = radixGenerator(brightness, RadixThemeColor.eggplant);
case ColorPreference.lime:
themeData = radixGenerator(brightness, RadixThemeColor.lime);
case ColorPreference.grim:
themeData = radixGenerator(brightness, RadixThemeColor.grim);
}
return themeData;
}
}

View File

@ -1,8 +1,9 @@
export 'animations.dart';
export 'desktop_control.dart';
export 'external_stream_state.dart';
export 'json_tools.dart';
export 'phono_byte.dart';
export 'protobuf_tools.dart';
export 'radix_generator.dart';
export 'responsive.dart';
export 'theme_service.dart';
export 'widget_helpers.dart';

View File

@ -1,6 +1,7 @@
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:quickalert/quickalert.dart';
extension BorderExt on Widget {
DecoratedBox debugBorder() => DecoratedBox(
@ -13,10 +14,29 @@ extension ModalProgressExt on Widget {
BlurryModalProgressHUD(
inAsyncCall: isLoading,
blurEffectIntensity: 4,
progressIndicator: SpinKitFoldingCube(
color: Theme.of(context).highlightColor,
size: 90,
),
progressIndicator: buildProgressIndicator(context),
color: Theme.of(context).shadowColor,
child: this);
}
Widget buildProgressIndicator(BuildContext context) => SpinKitFoldingCube(
color: Theme.of(context).highlightColor,
size: 90,
);
Widget waitingPage(BuildContext context) => ColoredBox(
color: Theme.of(context).scaffoldBackgroundColor,
child: Center(child: buildProgressIndicator(context)));
Future<void> showErrorModal(
BuildContext context, String title, String text) async {
await QuickAlert.show(
context: context,
type: QuickAlertType.error,
title: title,
text: text,
//backgroundColor: Colors.black,
//titleColor: Colors.white,
//textColor: Colors.white,
);
}

View File

@ -129,6 +129,17 @@ class DHTRecord {
return jsonDecodeBytes(fromJson, data);
}
Future<T?> getProtobuf<T extends GeneratedMessage>(
T Function(List<int> i) fromBuffer,
{int subkey = -1,
bool forceRefresh = false}) async {
final data = await get(subkey: subkey, forceRefresh: forceRefresh);
if (data == null) {
return null;
}
return fromBuffer(data.toList());
}
Future<void> eventualWriteBytes(Uint8List newValue, {int subkey = -1}) async {
subkey = subkeyOrDefault(subkey);
newValue = await crypto.encrypt(newValue, subkey);

View File

@ -233,6 +233,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.5"
circular_reveal_animation:
dependency: "direct main"
description:
name: circular_reveal_animation
sha256: "198f5a1fa27384dcf950807e0ae07a0da857c04df6233f7468755ee9db102b0c"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
cli_util:
dependency: transitive
description:
@ -669,6 +677,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
multi_split_view:
dependency: "direct main"
description:
name: multi_split_view
sha256: d68e129bff71fc9e6b66de59e1b79deaf4b91f30940130bfbd2d704c1c713499
url: "https://pub.dev"
source: hosted
version: "2.4.0"
octo_image:
dependency: transitive
description:
@ -1050,6 +1066,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
stylish_bottom_bar:
dependency: "direct main"
description:
name: stylish_bottom_bar
sha256: "54970e4753b4273239b6dea0d1175c56beabcf39b5c65df4cbf86f1b86568d2b"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
synchronized:
dependency: transitive
description:

View File

@ -16,6 +16,7 @@ dependencies:
change_case: ^1.1.0
charcode: ^1.3.1
circular_profile_avatar: ^2.0.5
circular_reveal_animation: ^2.0.1
cupertino_icons: ^1.0.2
equatable: ^2.0.5
fast_immutable_collections: ^9.1.5
@ -38,6 +39,7 @@ dependencies:
intl: ^0.18.0
json_annotation: ^4.8.1
loggy: ^2.0.3
multi_split_view: ^2.4.0
path: ^1.8.2
path_provider: ^2.0.11
protobuf: ^3.0.0
@ -47,6 +49,7 @@ dependencies:
riverpod_annotation: ^2.1.1
shared_preferences: ^2.0.15
signal_strength_indicator: ^0.4.1
stylish_bottom_bar: ^1.0.3
uuid: ^3.0.7
veilid:
# veilid: ^0.0.1