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

View file

@ -5,21 +5,45 @@ class Chat extends ConsumerWidget {
const Chat({super.key}); const Chat({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) => Scaffold( // ignore: prefer_expression_function_bodies
appBar: AppBar(title: const Text('Chat')), Widget build(BuildContext context, WidgetRef ref) {
body: const Center( //
child: Column( return Align(
mainAxisAlignment: MainAxisAlignment.center, alignment: AlignmentDirectional.centerEnd,
children: [ child: Container(
Text('Home Page'), decoration: BoxDecoration(
// ElevatedButton( color: Theme.of(context).scaffoldBackgroundColor,
// onPressed: () { ),
// ref.watch(authNotifierProvider.notifier).logout(); child: Stack(
// }, children: [
// 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 // Keep account hidden unless account password is entered
// (tries all hidden accounts with auth method (no biometrics)) // (tries all hidden accounts with auth method (no biometrics))
required bool hiddenAccount, required bool hiddenAccount,
// Display name for account until it is unlocked
required String name,
}) = _LocalAccount; }) = _LocalAccount;
factory LocalAccount.fromJson(dynamic json) => factory LocalAccount.fromJson(dynamic json) =>

View file

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

View file

@ -17,6 +17,7 @@ _$_LocalAccount _$$_LocalAccountFromJson(Map<String, dynamic> json) =>
EncryptionKeyType.fromJson(json['encryption_key_type']), EncryptionKeyType.fromJson(json['encryption_key_type']),
biometricsEnabled: json['biometrics_enabled'] as bool, biometricsEnabled: json['biometrics_enabled'] as bool,
hiddenAccount: json['hidden_account'] as bool, hiddenAccount: json['hidden_account'] as bool,
name: json['name'] as String,
); );
Map<String, dynamic> _$$_LocalAccountToJson(_$_LocalAccount instance) => Map<String, dynamic> _$$_LocalAccountToJson(_$_LocalAccount instance) =>
@ -29,4 +30,5 @@ Map<String, dynamic> _$$_LocalAccountToJson(_$_LocalAccount instance) =>
'encryption_key_type': instance.encryptionKeyType.toJson(), 'encryption_key_type': instance.encryptionKeyType.toJson(),
'biometrics_enabled': instance.biometricsEnabled, 'biometrics_enabled': instance.biometricsEnabled,
'hidden_account': instance.hiddenAccount, '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 // Theme supports light and dark mode, optionally selected by the
// operating system // operating system
enum DarkModePreference { enum BrightnessPreference {
system, system,
light, light,
dark; dark;
factory DarkModePreference.fromJson(dynamic j) => factory BrightnessPreference.fromJson(dynamic j) =>
DarkModePreference.values.byName((j as String).toCamelCase()); BrightnessPreference.values.byName((j as String).toCamelCase());
String toJson() => name.toPascalCase(); String toJson() => name.toPascalCase();
} }
@ -33,34 +33,20 @@ class LockPreference with _$LockPreference {
// Theme supports multiple color variants based on 'Radix' // Theme supports multiple color variants based on 'Radix'
enum ColorPreference { enum ColorPreference {
amber, // Radix Colors
blue, scarlet,
bronze, babydoll,
brown, vapor,
crimson,
cyan,
gold, gold,
grass, garden,
gray, forest,
green, arctic,
indigo, lapis,
eggplant,
lime, lime,
mauve, grim,
mint, // Accessible Colors
olive, contrast;
orange,
pink,
plum,
purple,
red,
sage,
sand,
sky,
slate,
teal,
tomato,
violet,
yellow;
factory ColorPreference.fromJson(dynamic j) => factory ColorPreference.fromJson(dynamic j) =>
ColorPreference.values.byName((j as String).toCamelCase()); ColorPreference.values.byName((j as String).toCamelCase());
@ -76,15 +62,25 @@ enum LanguagePreference {
String toJson() => name.toPascalCase(); 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 // Preferences are stored in a table locally and globally affect all
// accounts imported/added and the app in general // accounts imported/added and the app in general
@freezed @freezed
class Preferences with _$Preferences { class Preferences with _$Preferences {
const factory Preferences({ const factory Preferences({
required DarkModePreference darkMode, required ThemePreferences themePreferences,
required ColorPreference themeColor,
required LanguagePreference language, required LanguagePreference language,
required int displayScale,
required LockPreference locking, required LockPreference locking,
}) = _Preferences; }) = _Preferences;

View file

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

View file

@ -20,20 +20,31 @@ Map<String, dynamic> _$$_LockPreferenceToJson(_$_LockPreference instance) =>
'lock_with_system_lock': instance.lockWithSystemLock, '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 _$$_PreferencesFromJson(Map<String, dynamic> json) =>
_$_Preferences( _$_Preferences(
darkMode: DarkModePreference.fromJson(json['dark_mode']), themePreferences: ThemePreferences.fromJson(json['theme_preferences']),
themeColor: ColorPreference.fromJson(json['theme_color']),
language: LanguagePreference.fromJson(json['language']), language: LanguagePreference.fromJson(json['language']),
displayScale: json['display_scale'] as int,
locking: LockPreference.fromJson(json['locking']), locking: LockPreference.fromJson(json['locking']),
); );
Map<String, dynamic> _$$_PreferencesToJson(_$_Preferences instance) => Map<String, dynamic> _$$_PreferencesToJson(_$_Preferences instance) =>
<String, dynamic>{ <String, dynamic>{
'dark_mode': instance.darkMode.toJson(), 'theme_preferences': instance.themePreferences.toJson(),
'theme_color': instance.themeColor.toJson(),
'language': instance.language.toJson(), 'language': instance.language.toJson(),
'display_scale': instance.displayScale,
'locking': instance.locking.toJson(), '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_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import '../tools/desktop_control.dart';
import 'app.dart'; import 'app.dart';
import 'log/log.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'; import 'veilid_support/veilid_support.dart';
void main() async { void main() async {
@ -32,7 +32,7 @@ void main() async {
final initTheme = themeService.initial; final initTheme = themeService.initial;
// Manage window on desktop platforms // Manage window on desktop platforms
await setupDesktopWindow(); await WindowControl.initialize();
// Make localization delegate // Make localization delegate
final delegate = await LocalizationDelegate.create( final delegate = await LocalizationDelegate.create(

View file

@ -1,10 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.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 '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 '../tools/tools.dart';
import 'main_pager/main_pager.dart';
class HomePage extends ConsumerStatefulWidget { class HomePage extends ConsumerStatefulWidget {
const HomePage({super.key}); const HomePage({super.key});
@ -17,6 +21,9 @@ class HomePage extends ConsumerStatefulWidget {
class HomePageState extends ConsumerState<HomePage> class HomePageState extends ConsumerState<HomePage>
with TickerProviderStateMixin { with TickerProviderStateMixin {
final _unfocusNode = FocusNode(); final _unfocusNode = FocusNode();
final MultiSplitViewController _splitController = MultiSplitViewController(
areas: [Area(minimalSize: 300, weight: 0.25), Area(minimalSize: 300)]);
final scaffoldKey = GlobalKey<ScaffoldState>(); final scaffoldKey = GlobalKey<ScaffoldState>();
bool hasContainerTriggered = false; bool hasContainerTriggered = false;
final animationsMap = { final animationsMap = {
@ -38,12 +45,6 @@ class HomePageState extends ConsumerState<HomePage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// // On page load action.
// SchedulerBinding.instance.addPostFrameCallback((_) async {
// await actions.initialize(
// context,
// );
// });
setupAnimations( setupAnimations(
animationsMap.values.where((anim) => animationsMap.values.where((anim) =>
@ -52,7 +53,11 @@ class HomePageState extends ConsumerState<HomePage>
this, this,
); );
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); WidgetsBinding.instance.addPostFrameCallback((_) async {
setState(() {});
await ref.read(windowControlProvider.notifier).changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.normal);
});
} }
@override @override
@ -61,250 +66,66 @@ class HomePageState extends ConsumerState<HomePage>
super.dispose(); 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 @override
Widget build(BuildContext context) => Scaffold( Widget build(BuildContext context) {
key: scaffoldKey, ref.watch(windowControlProvider);
appBar: AppBar(title: const Text('VeilidChat')),
body: SafeArea( return SafeArea(
child: GestureDetector( child: GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
child: Stack( child: responsiveVisibility(
children: [ context: context,
if (responsiveVisibility( phone: false,
context: context, )
phone: false, ? buildTablet(context)
)) : buildPhone(context),
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(),
),
),
],
),
),
),
],
),
),
));
} }

View file

@ -1,16 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:radix_colors/radix_colors.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}); const IndexPage({super.key});
static const path = '/'; static const path = '/';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
enableTitleBar(false); ref.watch(windowControlProvider);
return Scaffold( return Scaffold(
body: DecoratedBox( body: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -34,8 +36,8 @@ class IndexPage extends StatelessWidget {
)), )),
Expanded( Expanded(
child: SvgPicture.asset( child: SvgPicture.asset(
'assets/images/title.svg', 'assets/images/title.svg',
)) ))
]))), ]))),
)); ));
} }

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 '../entities/proto.dart' as proto;
import '../providers/local_accounts.dart'; import '../providers/local_accounts.dart';
import '../providers/logins.dart'; import '../providers/logins.dart';
import '../providers/window_control.dart';
import '../tools/tools.dart'; import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart'; import '../veilid_support/veilid_support.dart';
@ -111,8 +112,7 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
enableTitleBar(true); ref.watch(windowControlProvider);
portraitOnly();
final localAccounts = ref.watch(localAccountsProvider); final localAccounts = ref.watch(localAccountsProvider);
final logins = ref.watch(loginsProvider); final logins = ref.watch(loginsProvider);
@ -132,15 +132,8 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
try { try {
await createAccount(); await createAccount();
} on Exception catch (e) { } on Exception catch (e) {
await QuickAlert.show( await showErrorModal(
context: context, context, translate('new_account_page.error'), 'Exception: $e');
type: QuickAlertType.error,
title: translate('new_account_page.error'),
text: 'Exception: $e',
//backgroundColor: Colors.black,
//titleColor: Colors.white,
//textColor: Colors.white,
);
} }
}, },
).paddingSymmetric(horizontal: 24, vertical: 8), ).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'; part 'local_accounts.g.dart';
const String veilidChatAccountKey = 'com.veilid.veilidchat';
// Local account manager // Local account manager
@riverpod @riverpod
class LocalAccounts extends _$LocalAccounts class LocalAccounts extends _$LocalAccounts
@ -90,6 +92,7 @@ class LocalAccounts extends _$LocalAccounts
encryptionKeyType: encryptionKeyType, encryptionKeyType: encryptionKeyType,
biometricsEnabled: false, biometricsEnabled: false,
hiddenAccount: false, hiddenAccount: false,
name: account.profile.name,
); );
/////// Add account with profile to DHT /////// Add account with profile to DHT
@ -114,8 +117,14 @@ class LocalAccounts extends _$LocalAccounts
await identityRec.eventualUpdateJson(Identity.fromJson, await identityRec.eventualUpdateJson(Identity.fromJson,
(oldIdentity) async { (oldIdentity) async {
final accountRecords = IMapOfSets.from(oldIdentity.accountRecords) final oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords);
.add('com.veilid.veilidchat', newAccountRecordInfo) // 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(); .asIMap();
return oldIdentity.copyWith(accountRecords: accountRecords); return oldIdentity.copyWith(accountRecords: accountRecords);
}); });
@ -151,3 +160,18 @@ class LocalAccounts extends _$LocalAccounts
/// Recover an account with the master identity secret /// 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 // 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]. /// See also [LocalAccounts].
@ProviderFor(LocalAccounts) @ProviderFor(LocalAccounts)

View file

@ -163,3 +163,18 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
state = AsyncValue.data(updated); 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 // 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'; String _$loginsHash() => r'ed9dbe91a248f662ccb0fac6edf5b1892cf2ef92';
/// See also [Logins]. /// See also [Logins].

View file

@ -1,3 +1,5 @@
export 'account_profile.dart';
export 'connection_state.dart'; export 'connection_state.dart';
export 'local_accounts.dart'; export 'local_accounts.dart';
export 'logins.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 // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$routerNotifierHash() => r'ef31219dde5e12b2bb224c79ca13ab4f414c81b4'; String _$routerNotifierHash() => r'8e7b9debfa144253e25871edf920bf315f28a861';
/// See also [RouterNotifier]. /// See also [RouterNotifier].
@ProviderFor(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 isAndroid => !kIsWeb && Platform.isAndroid;
bool get isiOS => !kIsWeb && Platform.isIOS; bool get isiOS => !kIsWeb && Platform.isIOS;
bool get isWeb => kIsWeb; bool get isWeb => kIsWeb;
bool get isDesktop =>
Platform.isWindows || Platform.isLinux || Platform.isMacOS;
const kMobileWidthCutoff = 479.0; 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 'animations.dart';
export 'desktop_control.dart';
export 'external_stream_state.dart'; export 'external_stream_state.dart';
export 'json_tools.dart'; export 'json_tools.dart';
export 'phono_byte.dart'; export 'phono_byte.dart';
export 'protobuf_tools.dart'; export 'protobuf_tools.dart';
export 'radix_generator.dart';
export 'responsive.dart'; export 'responsive.dart';
export 'theme_service.dart';
export 'widget_helpers.dart'; export 'widget_helpers.dart';

View file

@ -1,6 +1,7 @@
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart'; import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:quickalert/quickalert.dart';
extension BorderExt on Widget { extension BorderExt on Widget {
DecoratedBox debugBorder() => DecoratedBox( DecoratedBox debugBorder() => DecoratedBox(
@ -13,10 +14,29 @@ extension ModalProgressExt on Widget {
BlurryModalProgressHUD( BlurryModalProgressHUD(
inAsyncCall: isLoading, inAsyncCall: isLoading,
blurEffectIntensity: 4, blurEffectIntensity: 4,
progressIndicator: SpinKitFoldingCube( progressIndicator: buildProgressIndicator(context),
color: Theme.of(context).highlightColor,
size: 90,
),
color: Theme.of(context).shadowColor, color: Theme.of(context).shadowColor,
child: this); 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); 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 { Future<void> eventualWriteBytes(Uint8List newValue, {int subkey = -1}) async {
subkey = subkeyOrDefault(subkey); subkey = subkeyOrDefault(subkey);
newValue = await crypto.encrypt(newValue, subkey); newValue = await crypto.encrypt(newValue, subkey);

View file

@ -233,6 +233,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" 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: cli_util:
dependency: transitive dependency: transitive
description: description:
@ -669,6 +677,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" 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: octo_image:
dependency: transitive dependency: transitive
description: description:
@ -1050,6 +1066,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" 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: synchronized:
dependency: transitive dependency: transitive
description: description:

View file

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