✨ Account screen after logged in
This commit is contained in:
parent
d7d9e41db3
commit
057ab16381
@ -21,5 +21,6 @@
|
|||||||
"nickname": "Nickname",
|
"nickname": "Nickname",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"fieldCannotBeEmpty": "This field cannot be empty.",
|
"fieldCannotBeEmpty": "This field cannot be empty.",
|
||||||
"fieldEmailAddressMustBeValid": "The email address must be valid."
|
"fieldEmailAddressMustBeValid": "The email address must be valid.",
|
||||||
|
"logout": "Logout"
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/theme.dart';
|
import 'package:island/pods/theme.dart';
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@ -48,13 +49,22 @@ void main() async {
|
|||||||
|
|
||||||
final _appRouter = AppRouter();
|
final _appRouter = AppRouter();
|
||||||
|
|
||||||
class IslandApp extends ConsumerWidget {
|
class IslandApp extends HookConsumerWidget {
|
||||||
const IslandApp({super.key});
|
const IslandApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = ref.watch(themeProvider);
|
final theme = ref.watch(themeProvider);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
|
Future(() {
|
||||||
|
userNotifier.fetchUser();
|
||||||
|
print('user fetched');
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
theme: theme?.light,
|
theme: theme?.light,
|
||||||
darkTheme: theme?.dark,
|
darkTheme: theme?.dark,
|
||||||
|
42
lib/models/user.dart
Normal file
42
lib/models/user.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
|
|
||||||
|
part 'user.freezed.dart';
|
||||||
|
part 'user.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SnAccount with _$SnAccount {
|
||||||
|
const factory SnAccount({
|
||||||
|
required int id,
|
||||||
|
required String name,
|
||||||
|
required String nick,
|
||||||
|
required String language,
|
||||||
|
required bool isSuperuser,
|
||||||
|
required SnAccountProfile profile,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnAccount;
|
||||||
|
|
||||||
|
factory SnAccount.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnAccountFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SnAccountProfile with _$SnAccountProfile {
|
||||||
|
const factory SnAccountProfile({
|
||||||
|
required int id,
|
||||||
|
required String? firstName,
|
||||||
|
required String? middleName,
|
||||||
|
required String? lastName,
|
||||||
|
required String? bio,
|
||||||
|
required SnCloudFile? picture,
|
||||||
|
required SnCloudFile? background,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnAccountProfile;
|
||||||
|
|
||||||
|
factory SnAccountProfile.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnAccountProfileFromJson(json);
|
||||||
|
}
|
398
lib/models/user.freezed.dart
Normal file
398
lib/models/user.freezed.dart
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'user.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnAccount {
|
||||||
|
|
||||||
|
int get id; String get name; String get nick; String get language; bool get isSuperuser; SnAccountProfile get profile; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnAccount
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountCopyWith<SnAccount> get copyWith => _$SnAccountCopyWithImpl<SnAccount>(this as SnAccount, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnAccount to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,name,nick,language,isSuperuser,profile,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, profile: $profile, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnAccountCopyWith<$Res> {
|
||||||
|
factory $SnAccountCopyWith(SnAccount value, $Res Function(SnAccount) _then) = _$SnAccountCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
int id, String name, String nick, String language, bool isSuperuser, SnAccountProfile profile, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnAccountProfileCopyWith<$Res> get profile;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnAccountCopyWithImpl<$Res>
|
||||||
|
implements $SnAccountCopyWith<$Res> {
|
||||||
|
_$SnAccountCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnAccount _self;
|
||||||
|
final $Res Function(SnAccount) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnAccount
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? profile = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccountProfile,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnAccount
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountProfileCopyWith<$Res> get profile {
|
||||||
|
|
||||||
|
return $SnAccountProfileCopyWith<$Res>(_self.profile, (value) {
|
||||||
|
return _then(_self.copyWith(profile: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnAccount implements SnAccount {
|
||||||
|
const _SnAccount({required this.id, required this.name, required this.nick, required this.language, required this.isSuperuser, required this.profile, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
|
factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json);
|
||||||
|
|
||||||
|
@override final int id;
|
||||||
|
@override final String name;
|
||||||
|
@override final String nick;
|
||||||
|
@override final String language;
|
||||||
|
@override final bool isSuperuser;
|
||||||
|
@override final SnAccountProfile profile;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnAccount
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnAccountCopyWith<_SnAccount> get copyWith => __$SnAccountCopyWithImpl<_SnAccount>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnAccountToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,name,nick,language,isSuperuser,profile,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, profile: $profile, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnAccountCopyWith<$Res> implements $SnAccountCopyWith<$Res> {
|
||||||
|
factory _$SnAccountCopyWith(_SnAccount value, $Res Function(_SnAccount) _then) = __$SnAccountCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
int id, String name, String nick, String language, bool isSuperuser, SnAccountProfile profile, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnAccountProfileCopyWith<$Res> get profile;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnAccountCopyWithImpl<$Res>
|
||||||
|
implements _$SnAccountCopyWith<$Res> {
|
||||||
|
__$SnAccountCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnAccount _self;
|
||||||
|
final $Res Function(_SnAccount) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnAccount
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? profile = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnAccount(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccountProfile,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnAccount
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountProfileCopyWith<$Res> get profile {
|
||||||
|
|
||||||
|
return $SnAccountProfileCopyWith<$Res>(_self.profile, (value) {
|
||||||
|
return _then(_self.copyWith(profile: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnAccountProfile {
|
||||||
|
|
||||||
|
int get id; String? get firstName; String? get middleName; String? get lastName; String? get bio; SnCloudFile? get picture; SnCloudFile? get background; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCopyWithImpl<SnAccountProfile>(this as SnAccountProfile, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnAccountProfile to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,picture,background,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, picture: $picture, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnAccountProfileCopyWith<$Res> {
|
||||||
|
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
int id, String? firstName, String? middleName, String? lastName, String? bio, SnCloudFile? picture, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnAccountProfileCopyWithImpl<$Res>
|
||||||
|
implements $SnAccountProfileCopyWith<$Res> {
|
||||||
|
_$SnAccountProfileCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnAccountProfile _self;
|
||||||
|
final $Res Function(SnAccountProfile) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = freezed,Object? picture = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,middleName: freezed == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
|
if (_self.picture == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||||
|
return _then(_self.copyWith(picture: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get background {
|
||||||
|
if (_self.background == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
|
return _then(_self.copyWith(background: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnAccountProfile implements SnAccountProfile {
|
||||||
|
const _SnAccountProfile({required this.id, required this.firstName, required this.middleName, required this.lastName, required this.bio, required this.picture, required this.background, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
|
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
|
||||||
|
|
||||||
|
@override final int id;
|
||||||
|
@override final String? firstName;
|
||||||
|
@override final String? middleName;
|
||||||
|
@override final String? lastName;
|
||||||
|
@override final String? bio;
|
||||||
|
@override final SnCloudFile? picture;
|
||||||
|
@override final SnCloudFile? background;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnAccountProfileCopyWith<_SnAccountProfile> get copyWith => __$SnAccountProfileCopyWithImpl<_SnAccountProfile>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnAccountProfileToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,picture,background,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, picture: $picture, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfileCopyWith<$Res> {
|
||||||
|
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
int id, String? firstName, String? middleName, String? lastName, String? bio, SnCloudFile? picture, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnAccountProfileCopyWithImpl<$Res>
|
||||||
|
implements _$SnAccountProfileCopyWith<$Res> {
|
||||||
|
__$SnAccountProfileCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnAccountProfile _self;
|
||||||
|
final $Res Function(_SnAccountProfile) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = freezed,Object? picture = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnAccountProfile(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,middleName: freezed == middleName ? _self.middleName : middleName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
|
if (_self.picture == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||||
|
return _then(_self.copyWith(picture: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnAccountProfile
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get background {
|
||||||
|
if (_self.background == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
|
return _then(_self.copyWith(background: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
74
lib/models/user.g.dart
Normal file
74
lib/models/user.g.dart
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'user.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
name: json['name'] as String,
|
||||||
|
nick: json['nick'] as String,
|
||||||
|
language: json['language'] as String,
|
||||||
|
isSuperuser: json['is_superuser'] as bool,
|
||||||
|
profile: SnAccountProfile.fromJson(json['profile'] as Map<String, dynamic>),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'nick': instance.nick,
|
||||||
|
'language': instance.language,
|
||||||
|
'is_superuser': instance.isSuperuser,
|
||||||
|
'profile': instance.profile.toJson(),
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnAccountProfile(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
firstName: json['first_name'] as String?,
|
||||||
|
middleName: json['middle_name'] as String?,
|
||||||
|
lastName: json['last_name'] as String?,
|
||||||
|
bio: json['bio'] as String?,
|
||||||
|
picture:
|
||||||
|
json['picture'] == null
|
||||||
|
? null
|
||||||
|
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
|
||||||
|
background:
|
||||||
|
json['background'] == null
|
||||||
|
? null
|
||||||
|
: SnCloudFile.fromJson(
|
||||||
|
json['background'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'first_name': instance.firstName,
|
||||||
|
'middle_name': instance.middleName,
|
||||||
|
'last_name': instance.lastName,
|
||||||
|
'bio': instance.bio,
|
||||||
|
'picture': instance.picture?.toJson(),
|
||||||
|
'background': instance.background?.toJson(),
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
40
lib/pods/userinfo.dart
Normal file
40
lib/pods/userinfo.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/user.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
|
||||||
|
class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
||||||
|
final Ref _ref;
|
||||||
|
|
||||||
|
UserInfoNotifier(this._ref) : super(const AsyncValue.data(null));
|
||||||
|
|
||||||
|
Future<String?> getAccessToken() async {
|
||||||
|
final prefs = _ref.read(sharedPreferencesProvider);
|
||||||
|
return prefs.getString('dyn_user_atk');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchUser() async {
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
try {
|
||||||
|
final client = _ref.read(apiClientProvider);
|
||||||
|
final response = await client.get('/accounts/me');
|
||||||
|
final user = SnAccount.fromJson(response.data);
|
||||||
|
state = AsyncValue.data(user);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
print('Failed to fetch user: $error');
|
||||||
|
state = AsyncValue.error(error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logOut() async {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
final prefs = _ref.read(sharedPreferencesProvider);
|
||||||
|
await prefs.remove(kTokenPairStoreKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final userInfoProvider =
|
||||||
|
StateNotifierProvider<UserInfoNotifier, AsyncValue<SnAccount?>>(
|
||||||
|
(ref) => UserInfoNotifier(ref),
|
||||||
|
);
|
@ -8,8 +8,17 @@ class AppRouter extends RootStackRouter {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<AutoRoute> get routes => [
|
List<AutoRoute> get routes => [
|
||||||
AutoRoute(page: ExploreRoute.page, path: '/'),
|
AutoRoute(
|
||||||
AutoRoute(page: AccountRoute.page, path: '/account'),
|
page: ExploreRoute.page,
|
||||||
|
path: '/',
|
||||||
|
meta: {'bottomNav': true},
|
||||||
|
initial: true,
|
||||||
|
),
|
||||||
|
AutoRoute(
|
||||||
|
page: AccountRoute.page,
|
||||||
|
path: '/account',
|
||||||
|
meta: {'bottomNav': true},
|
||||||
|
),
|
||||||
AutoRoute(page: LoginRoute.page, path: '/auth/login'),
|
AutoRoute(page: LoginRoute.page, path: '/auth/login'),
|
||||||
AutoRoute(page: CreateAccountRoute.page, path: '/auth/create-account'),
|
AutoRoute(page: CreateAccountRoute.page, path: '/auth/create-account'),
|
||||||
];
|
];
|
||||||
|
@ -1,12 +1,102 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/route.gr.dart';
|
import 'package:island/route.gr.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class AccountScreen extends StatelessWidget {
|
class AccountScreen extends HookConsumerWidget {
|
||||||
const AccountScreen({super.key});
|
const AccountScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
|
if (!user.hasValue) return _UnauthorizedAccountScreen();
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: const Text('Account')),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (user.value!.profile.background != null)
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 7,
|
||||||
|
child: CloudFileWidget(
|
||||||
|
item: user.value!.profile.background!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
ProfilePictureWidget(
|
||||||
|
item: user.value!.profile.picture,
|
||||||
|
radius: 24,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
|
textBaseline: TextBaseline.alphabetic,
|
||||||
|
children: [
|
||||||
|
Text(user.value!.nick).bold().fontSize(16),
|
||||||
|
Text('@${user.value!.name}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
user.value!.profile.bio ?? 'No description yet.',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, vertical: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(LucideIcons.logOut),
|
||||||
|
trailing: const Icon(LucideIcons.chevronRight),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('logout').tr(),
|
||||||
|
subtitle: Text('Log out of your account.'),
|
||||||
|
onTap: () {
|
||||||
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
|
userNotifier.logOut();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(
|
||||||
|
horizontal: 8,
|
||||||
|
top: 8,
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UnauthorizedAccountScreen extends StatelessWidget {
|
||||||
|
const _UnauthorizedAccountScreen();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
@ -14,15 +104,23 @@ class AccountScreen extends StatelessWidget {
|
|||||||
body: Column(
|
body: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Login'),
|
leading: const Icon(LucideIcons.userPlus),
|
||||||
|
trailing: const Icon(LucideIcons.chevronRight),
|
||||||
|
title: Text('createAccount').tr(),
|
||||||
|
subtitle: Text('New to here? We got you covered!'),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(LoginRoute());
|
context.router.push(CreateAccountRoute());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('Create an account'),
|
leading: const Icon(LucideIcons.logIn),
|
||||||
|
trailing: const Icon(LucideIcons.chevronRight),
|
||||||
|
subtitle: Text('Existing user? We\'re welcome you back!'),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('login').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(CreateAccountRoute());
|
context.router.push(LoginRoute());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -9,7 +9,7 @@ import 'package:island/pods/network.dart';
|
|||||||
import 'package:island/route.gr.dart';
|
import 'package:island/route.gr.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class CreateAccountScreen extends HookConsumerWidget {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 26,
|
radius: 26,
|
||||||
child: Icon(MdiIcons.accountPlus, size: 28),
|
child: const Icon(LucideIcons.userPlus, size: 28),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@ -223,7 +223,10 @@ class CreateAccountScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text('termAcceptLink').tr(),
|
Text('termAcceptLink').tr(),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Icon(MdiIcons.launch, size: 14),
|
const Icon(
|
||||||
|
LucideIcons.externalLink,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -250,7 +253,7 @@ class CreateAccountScreen extends HookConsumerWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text("next").tr(),
|
Text("next").tr(),
|
||||||
Icon(MdiIcons.chevronRight),
|
const Icon(LucideIcons.chevronRight),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -8,9 +8,10 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:island/models/auth.dart';
|
import 'package:island/models/auth.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
@ -128,7 +129,8 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
|
setTokenPair(ref.watch(sharedPreferencesProvider), atk, rtk);
|
||||||
ref.invalidate(tokenPairProvider);
|
ref.invalidate(tokenPairProvider);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
// TODO userinfo
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
||||||
|
userNotifier.fetchUser();
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
@ -145,7 +147,7 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 26,
|
radius: 26,
|
||||||
child: Icon(MdiIcons.formTextboxPassword, size: 28),
|
child: const Icon(LucideIcons.squareAsterisk, size: 28),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@ -178,7 +180,10 @@ class _LoginCheckScreen extends HookConsumerWidget {
|
|||||||
onPressed: isBusy.value ? null : () => performCheckTicket(),
|
onPressed: isBusy.value ? null : () => performCheckTicket(),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [Text('next').tr(), Icon(MdiIcons.chevronRight)],
|
children: [
|
||||||
|
Text('next').tr(),
|
||||||
|
const Icon(LucideIcons.chevronRight),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -242,7 +247,7 @@ class _LoginPickerScreen extends HookConsumerWidget {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 26,
|
radius: 26,
|
||||||
child: Icon(MdiIcons.security, size: 28),
|
child: const Icon(LucideIcons.lock, size: 28),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@ -260,7 +265,7 @@ class _LoginPickerScreen extends HookConsumerWidget {
|
|||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
),
|
),
|
||||||
secondary: Icon(MdiIcons.fileQuestion),
|
secondary: const Icon(LucideIcons.shieldQuestion),
|
||||||
title: Text('unknown').tr(),
|
title: Text('unknown').tr(),
|
||||||
enabled: !ticket!.blacklistFactors.contains(x.id),
|
enabled: !ticket!.blacklistFactors.contains(x.id),
|
||||||
value: factorPicked.value == x.id,
|
value: factorPicked.value == x.id,
|
||||||
@ -288,7 +293,10 @@ class _LoginPickerScreen extends HookConsumerWidget {
|
|||||||
onPressed: isBusy.value ? null : () => performGetFactorCode(),
|
onPressed: isBusy.value ? null : () => performGetFactorCode(),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [Text('next'.tr()), Icon(MdiIcons.chevronRight)],
|
children: [
|
||||||
|
Text('next'.tr()),
|
||||||
|
const Icon(LucideIcons.chevronRight),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -375,7 +383,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
|||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 26,
|
radius: 26,
|
||||||
child: Icon(MdiIcons.login, size: 28),
|
child: const Icon(LucideIcons.logIn, size: 28),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@ -409,7 +417,10 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
|||||||
onPressed: isBusy.value ? null : () => performNewTicket(),
|
onPressed: isBusy.value ? null : () => performNewTicket(),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [Text('next').tr(), Icon(MdiIcons.chevronRight)],
|
children: [
|
||||||
|
Text('next').tr(),
|
||||||
|
const Icon(LucideIcons.chevronRight),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -440,7 +451,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text('termAcceptLink'.tr()),
|
Text('termAcceptLink'.tr()),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Icon(MdiIcons.launch, size: 14),
|
const Icon(LucideIcons.externalLink, size: 14),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -4,9 +4,11 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/route.gr.dart';
|
import 'package:island/route.gr.dart';
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@ -79,28 +81,54 @@ class WindowScaffold extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
body: SizedBox.expand(child: child),
|
body: SizedBox.expand(child: child),
|
||||||
key: rootScaffoldKey,
|
key: rootScaffoldKey,
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar:
|
||||||
destinations: [
|
router.current.meta['bottomNav'] == true || router.currentPath == '/'
|
||||||
NavigationDestination(icon: Icon(MdiIcons.compass), label: 'Explore'),
|
? AppBottomNavigationBar(router: router)
|
||||||
NavigationDestination(icon: Icon(MdiIcons.account), label: 'Account'),
|
: null,
|
||||||
],
|
);
|
||||||
onDestinationSelected: (idx) {
|
}
|
||||||
switch (idx) {
|
}
|
||||||
case 0:
|
|
||||||
router.replace(ExploreRoute());
|
class AppBottomNavigationBar extends HookConsumerWidget {
|
||||||
break;
|
const AppBottomNavigationBar({super.key, required this.router});
|
||||||
case 1:
|
|
||||||
router.replace(AccountRoute());
|
final AppRouter router;
|
||||||
break;
|
|
||||||
}
|
@override
|
||||||
},
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
),
|
final destination = useState(0);
|
||||||
|
|
||||||
|
return NavigationBar(
|
||||||
|
selectedIndex: destination.value,
|
||||||
|
destinations: [
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(LucideIcons.compass),
|
||||||
|
label: 'Explore',
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(LucideIcons.userCircle),
|
||||||
|
label: 'Account',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (idx) {
|
||||||
|
switch (idx) {
|
||||||
|
case 0:
|
||||||
|
destination.value = idx;
|
||||||
|
router.replace(ExploreRoute());
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
destination.value = idx;
|
||||||
|
router.replace(AccountRoute());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
|
||||||
import 'image.dart';
|
import 'image.dart';
|
||||||
import 'video.dart';
|
import 'video.dart';
|
||||||
@ -35,7 +35,7 @@ class CloudFileWidget extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return Placeholder();
|
return Text('Unable render for ${item.mimeType}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ class ProfilePictureWidget extends ConsumerWidget {
|
|||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
child:
|
child:
|
||||||
item == null
|
item == null
|
||||||
? Icon(MdiIcons.account)
|
? Icon(LucideIcons.userCircle)
|
||||||
: CloudFileWidget(item: item!),
|
: CloudFileWidget(item: item!),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
16
pubspec.lock
16
pubspec.lock
@ -733,6 +733,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
lucide_icons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: lucide_icons
|
||||||
|
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.257.0"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -757,14 +765,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.11.1"
|
||||||
material_design_icons_flutter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: material_design_icons_flutter
|
|
||||||
sha256: "6f986b7a51f3ad4c00e33c5c84e8de1bdd140489bbcdc8b66fc1283dad4dea5a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.7296"
|
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -63,13 +63,13 @@ dependencies:
|
|||||||
media_kit_libs_video: ^1.0.6
|
media_kit_libs_video: ^1.0.6
|
||||||
flutter_cache_manager: ^3.4.1
|
flutter_cache_manager: ^3.4.1
|
||||||
flutter_platform_alert: ^0.7.0
|
flutter_platform_alert: ^0.7.0
|
||||||
material_design_icons_flutter: ^7.0.7296
|
|
||||||
email_validator: ^3.0.0
|
email_validator: ^3.0.0
|
||||||
easy_localization: ^3.0.7+1
|
easy_localization: ^3.0.7+1
|
||||||
flutter_inappwebview: ^6.1.5
|
flutter_inappwebview: ^6.1.5
|
||||||
animations: ^2.0.11
|
animations: ^2.0.11
|
||||||
package_info_plus: ^8.3.0
|
package_info_plus: ^8.3.0
|
||||||
device_info_plus: ^11.4.0
|
device_info_plus: ^11.4.0
|
||||||
|
lucide_icons: ^0.257.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user