[Release] v4.2.0+40200006 (#296)

TWEAKS
- [Sonarr/Upcoming] If an episode has not aired, show it as "Upcoming" instead of "Not Downloaded"

FIXES
- [Accounts] Register device token to database for future notification support
- [Accounts] Improve validation for email and passwords
- [Radarr] Fix some durations being displayed twice
- [UI/TextField] Show errors as snackbar instead of in-line error
This commit is contained in:
Jagandeep Brar
2021-01-02 13:07:10 -06:00
committed by GitHub
parent 319b35d226
commit b76de299e2
24 changed files with 195 additions and 229 deletions

View File

@@ -79,7 +79,7 @@ class _State extends State<LidarrAddSearch> {
Widget _list(List<Widget> data) => LSListViewStickyHeader(
controller: _scrollController,
slivers: <Widget>[
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchBar,
children: data,
)

View File

@@ -115,7 +115,7 @@ class _State extends State<LidarrCatalogue> with AutomaticKeepAliveClientMixin {
return LSListViewStickyHeader(
controller: _scrollController,
slivers: <Widget>[
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchSortBar,
children: _children,
),

View File

@@ -118,7 +118,7 @@ class _State extends State<LidarrSearchResults> {
return LSListViewStickyHeader(
controller: _scrollController,
slivers: [
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchSortBar,
children: _children,
)

View File

@@ -106,7 +106,7 @@ class _State extends State<NZBGetHistory> with AutomaticKeepAliveClientMixin {
return LSListViewStickyHeader(
controller: _scrollController,
slivers: <Widget>[
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchBar,
children: _children,
),

View File

@@ -49,7 +49,7 @@ class RadarrMissingData {
return '\t\t$profile';
}
String get runtimeString => '\t${Constants.TEXT_BULLET}\t${runtime.lunaRuntime()}${runtime.lunaRuntime()}';
String get runtimeString => runtime.lunaRuntime().isEmpty ? '' : '\t${Constants.TEXT_BULLET}\t${runtime.lunaRuntime()}';
List<TextSpan> get subtitle {
DateTime now = DateTime.now();

View File

@@ -79,7 +79,7 @@ class _State extends State<RadarrAddSearch> {
Widget _list(List<Widget> data) => LSListViewStickyHeader(
controller: _scrollController,
slivers: <Widget>[
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchBar,
children: data,
),

View File

@@ -114,7 +114,7 @@ class _State extends State<RadarrCatalogue> with AutomaticKeepAliveClientMixin {
return LSListViewStickyHeader(
controller: _scrollController,
slivers: <Widget>[
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchSortBar,
children: _children,
),

View File

@@ -118,7 +118,7 @@ class _State extends State<RadarrSearchResults> {
return LSListViewStickyHeader(
controller: _scrollController,
slivers: [
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchSortBar,
children: _children,
)

View File

@@ -106,7 +106,7 @@ class _State extends State<SABnzbdHistory> with AutomaticKeepAliveClientMixin {
return LSListViewStickyHeader(
controller: _scrollController,
slivers: <Widget>[
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchBar,
children: _children,
),

View File

@@ -95,7 +95,7 @@ class _State extends State<SearchResults> {
return LSListViewStickyHeader(
controller: _scrollController,
slivers: [
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchSortBar,
children: _children,
)

View File

@@ -73,7 +73,7 @@ class _State extends State<SearchSearch> {
Widget _list(List<Widget> data) => LSListViewStickyHeader(
controller: _scrollController,
slivers: <Widget>[
LSStickyHeader(
LunaSliverStickyHeader(
header: _searchBar,
children: data,
),

View File

@@ -7,116 +7,135 @@ class SettingsAccountSignedOutBody extends StatefulWidget {
}
class _State extends State<SettingsAccountSignedOutBody> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
LunaLoadingState _state = LunaLoadingState.INACTIVE;
@override
Widget build(BuildContext context) => Form(
key: _formKey,
child: LSListView(
children: [
Padding(
child: Center(
child: Image.asset(
'assets/branding/splash.png',
width: 200.0,
),
),
padding: EdgeInsets.symmetric(vertical: 16.0),
),
AutofillGroup(
child: LSCard(
child: Column(
children: [
LSTextInputBar(
controller: _emailController,
isFormField: true,
margin: EdgeInsets.only(top: 12.0, bottom: 6.0, left: 12.0, right: 12.0),
labelIcon: Icons.person,
labelText: 'Email...',
action: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
autofillHints: [AutofillHints.username, AutofillHints.email],
onChanged: (value, updateController) => setState(() {
if(updateController) _emailController.text = value;
}),
validator: (value) {
if(value.isEmpty) return 'Email Required';
return null;
},
),
LSTextInputBar(
controller: _passwordController,
isFormField: true,
margin: EdgeInsets.only(top: 6.0, bottom: 12.0, left: 12.0, right: 12.0),
labelIcon: Icons.vpn_key,
labelText: 'Password...',
obscureText: true,
keyboardType: TextInputType.text,
autofillHints: [AutofillHints.password, AutofillHints.newPassword],
action: TextInputAction.done,
onChanged: (value, updateController) => setState(() {
if(updateController) _passwordController.text = value;
}),
validator: (value) {
if(value.isEmpty) return 'Password Required';
return null;
},
),
],
),
Widget build(BuildContext context) => LSListView(
children: [
Padding(
child: Center(
child: Image.asset(
'assets/branding/splash.png',
width: 200.0,
),
),
LSContainerRow(
children: [
Expanded(
child: LSButton(
text: 'Register',
backgroundColor: LunaColours.blueGrey,
onTap: _register,
reducedMargin: true,
isLoading: _state == LunaLoadingState.ACTIVE,
padding: EdgeInsets.symmetric(vertical: 16.0),
),
AutofillGroup(
child: LSCard(
child: Column(
children: [
LSTextInputBar(
controller: _emailController,
isFormField: true,
margin: EdgeInsets.all(12.0),
labelIcon: Icons.person,
labelText: 'Email',
action: TextInputAction.next,
keyboardType: TextInputType.emailAddress,
autofillHints: [AutofillHints.username, AutofillHints.email],
onChanged: (value, updateController) => setState(() {
if(updateController) _emailController.text = value;
}),
),
),
Expanded(
child: LSButton(
text: 'Sign In',
onTap: _signIn,
reducedMargin: true,
isLoading: _state == LunaLoadingState.ACTIVE,
LSTextInputBar(
controller: _passwordController,
isFormField: true,
margin: EdgeInsets.only(bottom: 12.0, left: 12.0, right: 12.0),
labelIcon: Icons.vpn_key,
labelText: 'Password',
obscureText: true,
keyboardType: TextInputType.text,
autofillHints: [AutofillHints.password, AutofillHints.newPassword],
action: TextInputAction.done,
onChanged: (value, updateController) => setState(() {
if(updateController) _passwordController.text = value;
}),
),
),
],
],
),
),
],
),
),
LSContainerRow(
children: [
Expanded(
child: LSButton(
text: 'Register',
backgroundColor: LunaColours.blueGrey,
onTap: _register,
reducedMargin: true,
isLoading: _state == LunaLoadingState.ACTIVE,
),
),
Expanded(
child: LSButton(
text: 'Sign In',
onTap: _signIn,
reducedMargin: true,
isLoading: _state == LunaLoadingState.ACTIVE,
),
),
],
),
],
);
bool _validateEmailAddress({ bool showSnackBarOnFailure = true }) {
const _regex = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)";
if(!RegExp(_regex).hasMatch(_emailController.text)) {
if(showSnackBarOnFailure) showLunaErrorSnackBar(
context: context,
title: 'Invalid Email',
message: 'The email address is invalid',
);
return false;
}
return true;
}
bool _validatePassword({ bool showSnackBarOnFailure = true }) {
if(_passwordController.text.isEmpty) {
if(showSnackBarOnFailure) showLunaErrorSnackBar(
context: context,
title: 'Invalid Password',
message: 'The password is invalid',
);
return false;
}
return true;
}
Future<void> _register() async {
// Set button state
if(!_validateEmailAddress() || !_validatePassword()) return;
if(mounted) setState(() => _state = LunaLoadingState.ACTIVE);
// Check form, then register user
if(_formKey.currentState.validate()) await LunaFirebaseAuth().registerUser(_emailController.text, _passwordController.text)
.then((response) => response.state
await LunaFirebaseAuth().registerUser(_emailController.text, _passwordController.text)
.then((response) {
if(mounted) setState(() => _state = LunaLoadingState.INACTIVE);
response.state
? showLunaSuccessSnackBar(context: context, title: 'Successfully Registered', message: response.user.email)
: showLunaErrorSnackBar(context: context, title: 'Failed to Register', message: response.error?.message ?? 'Unknown Error'))
.catchError((error, stack) => showLunaErrorSnackBar(context: context, title: 'Failed to Register', error: error));
// Set button state
if(mounted) setState(() => _state = LunaLoadingState.INACTIVE);
: showLunaErrorSnackBar(context: context, title: 'Failed to Register', message: response.error?.message ?? 'Unknown Error');
})
.catchError((error, stack) {
if(mounted) setState(() => _state = LunaLoadingState.INACTIVE);
showLunaErrorSnackBar(context: context, title: 'Failed to Register', error: error);
});
}
Future<void> _signIn() async {
// Set button state
if(!_validateEmailAddress() || !_validatePassword()) return;
if(mounted) setState(() => _state = LunaLoadingState.ACTIVE);
// Check form, then login user
if(_formKey.currentState.validate()) await LunaFirebaseAuth().signInUser(_emailController.text, _passwordController.text)
.then((response) => response.state
await LunaFirebaseAuth().signInUser(_emailController.text, _passwordController.text)
.then((response) {
if(mounted) setState(() => _state = LunaLoadingState.INACTIVE);
response.state
? showLunaSuccessSnackBar(context: context, title: 'Successfully Signed In', message: response.user.email)
: showLunaErrorSnackBar(context: context, title: 'Failed to Sign In', message: response.error?.message ?? 'Unknown Error'))
.catchError((error, stack) => showLunaErrorSnackBar(context: context, title: 'Failed to Sign In', error: error));
// Set button state
if(mounted) setState(() => _state = LunaLoadingState.INACTIVE);
: showLunaErrorSnackBar(context: context, title: 'Failed to Sign In', message: response.error?.message ?? 'Unknown Error');
})
.catchError((error, stack) {
if(mounted) setState(() => _state = LunaLoadingState.INACTIVE);
showLunaErrorSnackBar(context: context, title: 'Failed to Sign In', error: error);
});
}
}
}

View File

@@ -9,4 +9,9 @@ extension SonarrCalendarExtension on SonarrCalendar {
: DateFormat('hh:mm a').format(this.airDateUtc.toLocal());
return Constants.TEXT_EMDASH;
}
}
bool get lunaHasAired {
if(this.airDateUtc != null) return DateTime.now().isAfter(this.airDateUtc.toLocal());
return false;
}
}

View File

@@ -138,9 +138,9 @@ class _State extends State<SonarrUpcomingTile> {
children: [
if(!widget.record.hasFile) TextSpan(
style: TextStyle(
color: LunaColours.red,
color: widget.record.lunaHasAired ? LunaColours.red : LunaColours.blue,
),
text: 'Not Downloaded'
text: widget.record.lunaHasAired ? 'Not Downloaded' : 'Upcoming',
),
if(widget.record.hasFile) TextSpan(
style: TextStyle(