(feat): [Radarr] Ability to view and manage the queue (#414)

* (queue): Ability to view basic queue, delete queue record
* (fix): [UI] Show mouse cursor on ListTile when onTap or onLongPress are not null
* (fix): [UI] Fix height/width of sorting/filtering buttons, ensure consistent fixed-height for input bars
* (fix): [Lists] Remove default drag handles from ReorderableListView
* (fix): Further safe-guarded with platform checks
* (fix): Hide settings options on incompatible platforms
* (chore): Update CHANGELOG.md
* (fix): Switch from package_info to package_info_plus to support MacOS and other platforms
* (chore): Update packages
* (chore): Update Podfile
* (tweak): [Icons] Add font_awesome_flutter, change Icon() usages to FaIcon() to prepare
* (tweak): [Sonarr/Queue] Utilize a cleaner messages dialog view
* (feat): [Radarr/Queue] Add movie name and quality profile to collapsed tile
* (fix): [UI/AppBar] Back button was top-left aligned instead of centered
* (feat): [Icons] Add FontAwesome Pro icons
* (tweak): [Icons] Rollback to FontAwesome 5
* (tweak): [Icons] Rollback to FontAwesome 5
* (fix): [Icons] Revert back to system icons for now
* (fix): [Icons] Revert from FaIcon to Icon
* (tweak): [UI/Expandable Tile] Show two buttons per line, wrapping to a new line when necessary
* (feat): [Radarr] Queue implementation finished
* (fix): [Queue] Safe-guard some data model usages
* (chore): Update CHANGELOG.md
This commit is contained in:
Jagandeep Brar
2021-04-09 11:13:16 -05:00
committed by GitHub
parent 00a4f4c603
commit d4bd52832f
72 changed files with 53764 additions and 105 deletions

View File

@@ -4,3 +4,9 @@ android/key.properties
ios/ExportOptions.plist
ios/GoogleService-Info.plist
macos/GoogleService-Info.plist
vendor/font_awesome_flutter/icons.json
vendor/font_awesome_flutter/lib/fonts/fa-brands-400.ttf
vendor/font_awesome_flutter/lib/fonts/fa-duotone-900.ttf
vendor/font_awesome_flutter/lib/fonts/fa-light-300.ttf
vendor/font_awesome_flutter/lib/fonts/fa-regular-400.ttf
vendor/font_awesome_flutter/lib/fonts/fa-solid-900.ttf

11
.gitignore vendored
View File

@@ -48,9 +48,16 @@ ios/build/
/.blackbox/pubring.gpg~
/.blackbox/pubring.kbx~
/.blackbox/secring.gpg
/ios/ExportOptions.plist
/ios/GoogleService-Info.plist
/android/key.properties
/android/app/google-services.json
/android/key.jks
/ios/ExportOptions.plist
/ios/GoogleService-Info.plist
/macos/GoogleService-Info.plist
/vendor/font_awesome_flutter/lib/fonts/fa-brands-400.ttf
/vendor/font_awesome_flutter/lib/fonts/fa-duotone-900.ttf
/vendor/font_awesome_flutter/lib/fonts/fa-light-300.ttf
/vendor/font_awesome_flutter/lib/fonts/fa-regular-400.ttf
/vendor/font_awesome_flutter/lib/fonts/fa-solid-900.ttf
/vendor/font_awesome_flutter/lib/fonts/fa-thin-100.ttf
/vendor/font_awesome_flutter/icons.json

View File

@@ -3,16 +3,23 @@
## GitHub
#### NEW
- `[Radarr/Queue]` Ability to view and manage the queue
#### TWEAKS
#### FIXES
- `[Radarr/Catalogue]` Update filters to more closely match the web GUI filtering
- `[UI/UX]` Search and filter/sort buttons now have fixed heights to have consistency across platforms
- `[UI/UX]` Show click cursor on all clickable tiles
- `[UI/UX]` Show two buttons per line on expandable tiles
#### PLATFORM SPECIFIC
- `[macOS]` (new) Added LunaSea icon
- `[macOS]` (fix) Set application/window title
- `[macOS]` (fix) Add platform compatability checks for Firebase Analytics & in-app purchases
- `[macOS]` (fix) Utilize package_info_plus to allow internal version checking
- `[macOS]` (fix) Hide incompatible settings options
---
## v5.0.0 (50000014)

View File

@@ -49,6 +49,7 @@
"radarr.InCinemasIn": "In Cinemas in {}",
"radarr.InCinemasToday": "In Cinemas Today",
"radarr.Interactive": "Interactive",
"radarr.Language": "Language",
"radarr.Languages": "Languages",
"radarr.ManualImport": "Manual Import",
"radarr.ManualImportDescription": "Import Movies from the Filesystem",

View File

@@ -66,7 +66,7 @@ PODS:
- Firebase/Analytics (= 7.3.0)
- firebase_core
- Flutter
- firebase_auth (1.0.2):
- firebase_auth (1.0.3):
- Firebase/Auth (= 7.3.0)
- firebase_core
- Flutter
@@ -81,7 +81,7 @@ PODS:
- Firebase/Messaging (= 7.3.0)
- firebase_core
- Flutter
- firebase_storage (8.0.2):
- firebase_storage (8.0.3):
- Firebase/Storage (= 7.3.0)
- firebase_core
- Flutter
@@ -187,7 +187,7 @@ PODS:
- nanopb/encode (2.30906.0)
- network_info_plus (0.0.1):
- Flutter
- package_info (0.0.1):
- package_info_plus (0.4.5):
- Flutter
- path_provider (0.0.1):
- Flutter
@@ -222,7 +222,7 @@ DEPENDENCIES:
- GoogleUtilities
- in_app_purchase (from `.symlinks/plugins/in_app_purchase/ios`)
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- quick_actions (from `.symlinks/plugins/quick_actions/ios`)
- share (from `.symlinks/plugins/share/ios`)
@@ -281,8 +281,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/in_app_purchase/ios"
network_info_plus:
:path: ".symlinks/plugins/network_info_plus/ios"
package_info:
:path: ".symlinks/plugins/package_info/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
quick_actions:
@@ -307,11 +307,11 @@ SPEC CHECKSUMS:
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Firebase: 26223c695fe322633274198cb19dca8cb7e54416
firebase_analytics: ee4abb07bfa857e7c95d97144efe9eb68be414cd
firebase_auth: 9e9e667b904ee5701ab50330a5718cce0bf1a469
firebase_auth: c2c27e1081671b02f90981e70dad54722198491f
firebase_core: b5d81dfd4fb2d6f700e67de34d9a633ae325c4e9
firebase_crashlytics: 3b8018f6c88a59be95f9e2f38f04e5c0f05e0c6a
firebase_messaging: 7547c99f31466371f9cfcb733d5a1bf32a819872
firebase_storage: 78b9c7d73a4e75a9bed8697fab8424979e31d7dd
firebase_storage: 2ce2146c1fa764e68330e6998fa386522c95d199
FirebaseAnalytics: 2580c2d62535ae7b644143d48941fcc239ea897a
FirebaseAuth: c224a0cf1afa0949bd5c7bfcf154b4f5ce8ddef2
FirebaseCore: 4d3c72622ce0e2106aaa07bb4b2935ba2c370972
@@ -330,7 +330,7 @@ SPEC CHECKSUMS:
in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6
nanopb: 1bf24dd71191072e120b83dd02d08f3da0d65e53
network_info_plus: b78876159360f5580608c2cea620d6ceffabd0ad
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
quick_actions: 6cb2390c4dab0e737c94573c27e18d9666710720

View File

@@ -452,7 +452,7 @@
"${BUILT_PRODUCTS_DIR}/in_app_purchase/in_app_purchase.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
"${BUILT_PRODUCTS_DIR}/network_info_plus/network_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
"${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
"${BUILT_PRODUCTS_DIR}/quick_actions/quick_actions.framework",
"${BUILT_PRODUCTS_DIR}/share/share.framework",
@@ -483,7 +483,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/network_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/quick_actions.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework",

View File

@@ -91,6 +91,25 @@ class LunaDialogs {
);
}
Future<void> showMessages(BuildContext context, List<String> messages) async {
if((messages ?? []).isEmpty) return textPreview(context, 'Messages', 'No messages found');
await LunaDialog.dialog(
context: context,
title: 'Messages',
cancelButtonText: 'Close',
content: List.generate(
messages.length,
(index) => LunaDialog.tile(
text: messages[index],
icon: Icons.info_outline_rounded,
iconColor: LunaColours.accent,
),
),
contentPadding: LunaDialog.listDialogContentPadding(),
);
}
/// **Will be removed in future**
///
/// Show a delete catalogue with all files warning dialog.

View File

@@ -29,5 +29,7 @@ class LunaFirebaseAnalytics {
}
/// Log an "app_open" event.
Future<void> appOpened() async => instance.logAppOpen();
Future<void> appOpened() async {
if(isPlatformCompatible) instance.logAppOpen();
}
}

View File

@@ -1,8 +1,12 @@
import 'dart:io';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseCrashlytics {
/// Returns true if Firebase Analytics is compatible with this build system/platform.
static bool get isPlatformCompatible => !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows;
/// Returns an instance of [FirebaseCrashlytics].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
@@ -11,8 +15,10 @@ class LunaFirebaseCrashlytics {
/// Set the enabled state of Firebase Crashlytics.
///
/// If `enabled` is set to false, force-disables Analytics.
void setEnabledState({ bool enabled = true }) {
bool state = kReleaseMode && LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS.data;
instance.setCrashlyticsCollectionEnabled(state && enabled);
void setEnabledState() {
if(isPlatformCompatible) {
bool state = kReleaseMode && LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS.data;
instance.setCrashlyticsCollectionEnabled(state);
}
}
}

View File

@@ -1,20 +1,23 @@
import 'dart:io';
import 'package:lunasea/core.dart';
import 'package:quick_actions/quick_actions.dart';
class LunaQuickActions {
static final QuickActions _quickActions = QuickActions();
static bool get isPlatformCompatible => Platform.isAndroid || Platform.isIOS;
/// Initialize the quick actions by setting the action handler.
///
/// Required before the handler can actually receive any intents.
void initialize() {
_quickActions.initialize((action) => _handler(action));
if(isPlatformCompatible) _quickActions.initialize((action) => _handler(action));
setShortcutItems();
}
/// Sets the shortcut items by checking the database and enabling the respective action if enabled.
void setShortcutItems() {
_quickActions.setShortcutItems(<ShortcutItem>[
if(isPlatformCompatible) _quickActions.setShortcutItems(<ShortcutItem>[
if(LunaDatabaseValue.QUICK_ACTIONS_SEARCH.data) LunaModule.SEARCH.shortcutItem,
if(LunaDatabaseValue.QUICK_ACTIONS_LIDARR.data) LunaModule.LIDARR.shortcutItem,
if(LunaDatabaseValue.QUICK_ACTIONS_RADARR.data) LunaModule.RADARR.shortcutItem,

View File

@@ -204,7 +204,9 @@ class _State extends State<LunaAppBar> {
},
);
return InkWell(
child: Icon(Icons.arrow_back_ios_rounded),
child: Center(
child: Icon(Icons.arrow_back_ios_rounded),
),
onTap: () async {
HapticFeedback.lightImpact();
Navigator.of(context).lunaSafetyPop();

View File

@@ -66,21 +66,25 @@ class _State extends State<LunaBottomNavigationBar> {
child: GNav(
gap: 8.0,
iconSize: 24.0,
padding: EdgeInsets.fromLTRB(18.0, 10.0, 12.0, 10.0),
duration: Duration(milliseconds: LunaUI.ANIMATION_SPEED),
tabBackgroundColor: Theme.of(context).canvasColor.withOpacity(LunaUI.BUTTON_BACKGROUND_OPACITY),
activeColor: LunaColours.accent,
tabs: List.generate(widget.icons.length, (index) => GButton(
icon: widget.icons[index],
text: widget.titles[index],
active: _index == index,
iconSize: 22.0,
haptic: true,
padding: _index == index
? EdgeInsets.all(10.0).add(EdgeInsets.only(left: 6.0))
: EdgeInsets.all(10.0),
iconColor: Colors.white,
textStyle: TextStyle(
fontWeight: LunaUI.FONT_WEIGHT_BOLD,
fontSize: LunaUI.FONT_SIZE_NAVIGATION_BAR,
color: Colors.white,
),
iconActiveColor: LunaColours.accent,
leading: widget.leadingOnTab == null ? null : widget.leadingOnTab[index],
)).toList(),
tabActiveBorder: LunaUI.shouldUseBorder ? Border.all(color: Colors.white12) : null,

View File

@@ -10,9 +10,15 @@ class LunaCard extends Card {
Color color,
Decoration decoration,
double height,
double width,
}) : super(
key: key,
child: Container(child: child, decoration: decoration, height: height),
child: Container(
child: child,
decoration: decoration,
height: height,
width: width,
),
margin: margin,
color: color == null ? Theme.of(context).primaryColor : color,
shape: LunaUI.shapeBorder,

View File

@@ -1,4 +1,5 @@
import 'package:flutter/widgets.dart';
// export 'package:font_awesome_flutter/font_awesome_flutter.dart';
class LunaIcons {
LunaIcons._();

View File

@@ -3,6 +3,7 @@ import 'package:lunasea/core.dart';
class LunaTextInputBar extends StatefulWidget {
static const double appBarHeight = 62.0;
static const double appBarInnerHeight = 62.0-13.0;
static const EdgeInsets appBarMargin = const EdgeInsets.fromLTRB(12.0, 1.0, 12.0, 12.0);
final TextEditingController controller;
@@ -56,6 +57,7 @@ class _State extends State<LunaTextInputBar> {
child: widget.isFormField ? _isForm : _isNotForm,
),
),
height: LunaTextInputBar.appBarInnerHeight,
color: Theme.of(context).canvasColor,
);

View File

@@ -26,6 +26,7 @@ class LunaListTile extends Card {
subtitle: subtitle,
trailing: trailing,
leading: leading,
mouseCursor: onTap != null || onLongPress != null ? SystemMouseCursors.click : null,
contentPadding: contentPadding ? customContentPadding ?? EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0) : null,
),
borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS),

View File

@@ -7,6 +7,7 @@ class LunaReorderableListView extends StatelessWidget {
final ScrollPhysics physics;
final ScrollController controller;
final void Function(int, int) onReorder;
final bool buildDefaultDragHandles;
LunaReorderableListView({
Key key,
@@ -15,6 +16,7 @@ class LunaReorderableListView extends StatelessWidget {
@required this.onReorder,
this.padding,
this.physics = const AlwaysScrollableScrollPhysics(),
this.buildDefaultDragHandles = false,
}) : super(key: key);
@override
@@ -28,6 +30,7 @@ class LunaReorderableListView extends StatelessWidget {
padding: padding ?? MediaQuery.of(context).padding.add(EdgeInsets.symmetric(vertical: 8.0)),
physics: physics,
onReorder: onReorder,
buildDefaultDragHandles: buildDefaultDragHandles,
),
);
}

View File

@@ -8,6 +8,7 @@ class LunaReorderableListViewBuilder extends StatelessWidget {
final ScrollPhysics physics;
final ScrollController controller;
final void Function(int, int) onReorder;
final bool buildDefaultDragHandles;
LunaReorderableListViewBuilder({
Key key,
@@ -17,6 +18,7 @@ class LunaReorderableListViewBuilder extends StatelessWidget {
@required this.onReorder,
this.padding,
this.physics = const AlwaysScrollableScrollPhysics(),
this.buildDefaultDragHandles = false,
}) : super(key: key) {
assert(itemCount != null);
assert(itemBuilder != null);
@@ -34,6 +36,7 @@ class LunaReorderableListViewBuilder extends StatelessWidget {
itemCount: itemCount,
itemBuilder: itemBuilder,
onReorder: onReorder,
buildDefaultDragHandles: buildDefaultDragHandles,
),
);
}

View File

@@ -20,7 +20,7 @@ class LunaTheme {
accentColor: LunaColours.accent,
highlightColor: LunaColours.splash.withOpacity(0.15),
cardColor: LunaColours.secondary,
hoverColor: LunaColours.splash.withOpacity(0.10),
hoverColor: LunaColours.accent.withOpacity(0.15),
splashColor: LunaColours.accent.withOpacity(0.30),
dialogBackgroundColor: LunaColours.secondary,
toggleableActiveColor: LunaColours.accent,
@@ -43,7 +43,7 @@ class LunaTheme {
accentColor: LunaColours.accent,
highlightColor: LunaColours.splash.withOpacity(0.15),
cardColor: Colors.black,
hoverColor: LunaColours.splash.withOpacity(0.10),
hoverColor: LunaColours.accent.withOpacity(0.15),
splashColor: LunaColours.accent.withOpacity(0.30),
dialogBackgroundColor: Colors.black,
toggleableActiveColor: LunaColours.accent,

View File

@@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:package_info/package_info.dart';
import 'package:package_info_plus/package_info_plus.dart';
class LunaChangelog {
/// Check if the current build number is not equal to the stored build number.
@@ -10,7 +10,7 @@ class LunaChangelog {
Future<void> checkAndShowChangelog() async {
PackageInfo.fromPlatform()
.then((package) {
if(AlertsDatabaseValue.CHANGELOG.data != package.buildNumber) {
if(package != null && AlertsDatabaseValue.CHANGELOG.data != package.buildNumber) {
AlertsDatabaseValue.CHANGELOG.put(package.buildNumber);
showChangelog(package.version, package.buildNumber);
}
@@ -32,7 +32,8 @@ class LunaChangelog {
body: LunaListViewModal(
shrinkWrap: false,
children: [
LunaHeader(text: '$version ($buildNumber)', subtitle: changelog.motd),
if(version != null && buildNumber != null) LunaHeader(text: '$version ($buildNumber)', subtitle: changelog.motd),
if(version == null || buildNumber == null) LunaHeader(text: 'LunaSea', subtitle: changelog.motd),
if((changelog.changesNew?.length ?? 0) != 0) LunaHeader(text: 'New'),
if((changelog.changesNew?.length ?? 0) != 0) LunaTableCard(
content: List<LunaTableContent>.generate(

View File

@@ -61,6 +61,7 @@ class LunaLogger {
Future<void> recordCrashlyticsError(dynamic error, StackTrace stackTrace) async {
if(
kReleaseMode &&
LunaFirebaseCrashlytics.isPlatformCompatible &&
LunaFirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled &&
LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS.data &&
!(error is DioError)
@@ -75,6 +76,7 @@ class LunaLogger {
Future<void> recordCrashlyticsFlutterError(FlutterErrorDetails details) async {
if(
kReleaseMode &&
LunaFirebaseCrashlytics.isPlatformCompatible &&
LunaFirebaseCrashlytics.instance.isCrashlyticsCollectionEnabled &&
LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS.data &&
!(error is DioError)

View File

@@ -133,14 +133,22 @@ class _State extends State<LunaExpandableListTile> {
padding: EdgeInsets.symmetric(horizontal: 12.0),
)).toList(),
if(widget.expandedTableButtons != null) Padding(
child: Row(
child: Wrap(
children: [
...widget.expandedTableButtons.map<Widget>((button) => Expanded(
child: Padding(
child: button,
padding: EdgeInsets.zero,
),
)).toList(),
...List.generate(
widget.expandedTableButtons.length,
(index) {
double widthFactor = 0.5;
if(
index == (widget.expandedTableButtons.length-1) &&
widget.expandedTableButtons.length.isOdd
) widthFactor = 1;
return FractionallySizedBox(
child: widget.expandedTableButtons[index],
widthFactor: widthFactor,
);
}
),
],
),
padding: EdgeInsets.symmetric(horizontal: 6.0),

View File

@@ -129,16 +129,6 @@ class _State extends State<DashboardCalendarWidget> {
weekendStyle: weekdayTitleStyle,
weekdayStyle: weekdayTitleStyle,
),
headerStyle: HeaderStyle(
titleTextStyle: TextStyle(
fontSize: LunaUI.FONT_SIZE_HEADER,
fontWeight: LunaUI.FONT_WEIGHT_BOLD,
),
centerHeaderTitle: true,
formatButtonVisible: false,
leftChevronIcon: Icon(Icons.arrow_back_ios_rounded),
rightChevronIcon: Icon(Icons.arrow_forward_ios_rounded),
),
initialCalendarFormat: (DashboardDatabaseValue.CALENDAR_STARTING_SIZE.data as CalendarStartingSize).data,
availableCalendarFormats: const {
CalendarFormat.month : 'Month', CalendarFormat.twoWeeks : '2 Weeks', CalendarFormat.week : 'Week'},

View File

@@ -29,6 +29,8 @@ class _State extends State<LidarrCatalogueHideButton> {
borderRadius: BorderRadius.circular(LunaUI.BORDER_RADIUS),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.symmetric(horizontal: 12.0),
color: Theme.of(context).canvasColor,
);

View File

@@ -18,6 +18,8 @@ class _State extends State<LidarrCatalogueSortButton> {
@override
Widget build(BuildContext context) => LunaCard(
context: context,
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
child: Consumer<LidarrState>(
builder: (context, model, _) => LunaPopupMenuButton<LidarrCatalogueSorting>(
tooltip: 'Sort Catalogue',

View File

@@ -26,6 +26,8 @@ class _State extends State<LidarrReleasesHideButton> {
onPressed: () => model.hideRejectedReleases = !model.hideRejectedReleases,
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: LunaTextInputBar.appBarMargin.subtract(EdgeInsets.only(left: 12.0)),
color: Theme.of(context).canvasColor,
);

View File

@@ -18,6 +18,8 @@ class _State extends State<LidarrReleasesSortButton> {
@override
Widget build(BuildContext context) => LunaCard(
context: context,
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
child: Consumer<LidarrState>(
builder: (context, model, _) => LunaPopupMenuButton<LidarrReleasesSorting>(
tooltip: 'Sort Releases',

View File

@@ -26,6 +26,8 @@ class _State extends State<NZBGetHistoryHideButton> {
onPressed: () => model.historyHideFailed = !model.historyHideFailed,
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.only(left: 12.0),
color: Theme.of(context).canvasColor,
);

View File

@@ -44,6 +44,9 @@ class RadarrDatabase extends LunaModuleDatabase {
case RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS:
case RadarrDatabaseValue.ADD_DISCOVER_USE_SUGGESTIONS:
case RadarrDatabaseValue.MANUAL_IMPORT_DEFAULT_MODE:
case RadarrDatabaseValue.QUEUE_REFRESH_RATE:
case RadarrDatabaseValue.QUEUE_BLACKLIST:
case RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT:
case RadarrDatabaseValue.REMOVE_MOVIE_IMPORT_LIST:
case RadarrDatabaseValue.REMOVE_MOVIE_DELETE_FILES:
case RadarrDatabaseValue.CONTENT_PAGE_SIZE: data[value.key] = value.data; break;
@@ -76,6 +79,9 @@ class RadarrDatabase extends LunaModuleDatabase {
case RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS:
case RadarrDatabaseValue.ADD_DISCOVER_USE_SUGGESTIONS:
case RadarrDatabaseValue.MANUAL_IMPORT_DEFAULT_MODE:
case RadarrDatabaseValue.QUEUE_REFRESH_RATE:
case RadarrDatabaseValue.QUEUE_BLACKLIST:
case RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT:
case RadarrDatabaseValue.REMOVE_MOVIE_DELETE_FILES:
case RadarrDatabaseValue.REMOVE_MOVIE_IMPORT_LIST:
case RadarrDatabaseValue.CONTENT_PAGE_SIZE: value.put(config[key]); break;
@@ -103,6 +109,9 @@ class RadarrDatabase extends LunaModuleDatabase {
case 'RADARR_ADD_MOVIE_DEFAULT_TAGS': return RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS;
case 'RADARR_ADD_DISCOVER_USE_SUGGESTIONS': return RadarrDatabaseValue.ADD_DISCOVER_USE_SUGGESTIONS;
case 'RADARR_MANUAL_IMPORT_DEFAULT_MODE': return RadarrDatabaseValue.MANUAL_IMPORT_DEFAULT_MODE;
case 'RADARR_QUEUE_REFRESH_RATE': return RadarrDatabaseValue.QUEUE_REFRESH_RATE;
case 'RADARR_QUEUE_BLACKLIST': return RadarrDatabaseValue.QUEUE_BLACKLIST;
case 'RADARR_QUEUE_REMOVE_FROM_CLIENT': return RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT;
case 'RADARR_REMOVE_MOVIE_IMPORT_LIST': return RadarrDatabaseValue.REMOVE_MOVIE_IMPORT_LIST;
case 'RADARR_REMOVE_MOVIE_DELETE_FILES': return RadarrDatabaseValue.REMOVE_MOVIE_DELETE_FILES;
case 'RADARR_CONTENT_PAGE_SIZE': return RadarrDatabaseValue.CONTENT_PAGE_SIZE;
@@ -129,6 +138,9 @@ enum RadarrDatabaseValue {
ADD_MOVIE_DEFAULT_TAGS,
ADD_DISCOVER_USE_SUGGESTIONS,
MANUAL_IMPORT_DEFAULT_MODE,
QUEUE_REFRESH_RATE,
QUEUE_BLACKLIST,
QUEUE_REMOVE_FROM_CLIENT,
REMOVE_MOVIE_IMPORT_LIST,
REMOVE_MOVIE_DELETE_FILES,
CONTENT_PAGE_SIZE,
@@ -154,6 +166,9 @@ extension RadarrDatabaseValueExtension on RadarrDatabaseValue {
case RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS: return 'RADARR_ADD_MOVIE_DEFAULT_TAGS';
case RadarrDatabaseValue.ADD_DISCOVER_USE_SUGGESTIONS: return 'RADARR_ADD_DISCOVER_USE_SUGGESTIONS';
case RadarrDatabaseValue.MANUAL_IMPORT_DEFAULT_MODE: return 'RADARR_MANUAL_IMPORT_DEFAULT_MODE';
case RadarrDatabaseValue.QUEUE_REFRESH_RATE: return 'RADARR_QUEUE_REFRESH_RATE';
case RadarrDatabaseValue.QUEUE_BLACKLIST: return 'RADARR_RADARR_QUEUE_BLACKLIST';
case RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT: return 'RADARR_QUEUE_REMOVE_FROM_CLIENT';
case RadarrDatabaseValue.REMOVE_MOVIE_IMPORT_LIST: return 'RADARR_REMOVE_MOVIE_IMPORT_LIST';
case RadarrDatabaseValue.REMOVE_MOVIE_DELETE_FILES: return 'RADARR_REMOVE_MOVIE_DELETE_FILES';
case RadarrDatabaseValue.CONTENT_PAGE_SIZE: return 'RADARR_CONTENT_PAGE_SIZE';
@@ -181,6 +196,9 @@ extension RadarrDatabaseValueExtension on RadarrDatabaseValue {
case RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS: return _box.get(this.key, defaultValue: []);
case RadarrDatabaseValue.ADD_DISCOVER_USE_SUGGESTIONS: return _box.get(this.key, defaultValue: true);
case RadarrDatabaseValue.MANUAL_IMPORT_DEFAULT_MODE: return _box.get(this.key, defaultValue: RadarrImportMode.COPY.value);
case RadarrDatabaseValue.QUEUE_REFRESH_RATE: return _box.get(this.key, defaultValue: 60);
case RadarrDatabaseValue.QUEUE_BLACKLIST: return _box.get(this.key, defaultValue: false);
case RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT: return _box.get(this.key, defaultValue: false);
case RadarrDatabaseValue.REMOVE_MOVIE_DELETE_FILES: return _box.get(this.key, defaultValue: false);
case RadarrDatabaseValue.REMOVE_MOVIE_IMPORT_LIST: return _box.get(this.key, defaultValue: false);
case RadarrDatabaseValue.CONTENT_PAGE_SIZE: return _box.get(this.key, defaultValue: 25);
@@ -208,6 +226,9 @@ extension RadarrDatabaseValueExtension on RadarrDatabaseValue {
case RadarrDatabaseValue.ADD_MOVIE_DEFAULT_TAGS: if(value is List) box.put(this.key, value); return;
case RadarrDatabaseValue.ADD_DISCOVER_USE_SUGGESTIONS: if(value is bool) box.put(this.key, value); return;
case RadarrDatabaseValue.MANUAL_IMPORT_DEFAULT_MODE: if(value is RadarrImportMode) box.put(this.key, value.value); return;
case RadarrDatabaseValue.QUEUE_REFRESH_RATE: if(value is int) box.put(this.key, value); return;
case RadarrDatabaseValue.QUEUE_BLACKLIST: if(value is bool) box.put(this.key, value); return;
case RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT: if(value is bool) box.put(this.key, value); return;
case RadarrDatabaseValue.REMOVE_MOVIE_DELETE_FILES: if(value is bool) box.put(this.key, value); return;
case RadarrDatabaseValue.REMOVE_MOVIE_IMPORT_LIST: if(value is bool) box.put(this.key, value); return;
case RadarrDatabaseValue.CONTENT_PAGE_SIZE: if(value is int) box.put(this.key, value); return;

View File

@@ -427,6 +427,45 @@ class RadarrDialogs {
);
}
Future<bool> confirmDeleteQueue(BuildContext context) async {
bool _flag = false;
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context, rootNavigator: true).pop();
}
await LunaDialog.dialog(
context: context,
title: 'Remove From Queue',
buttons: [
LunaDialog.button(
text: 'Remove',
textColor: LunaColours.red,
onPressed: () => _setValues(true),
),
],
content: [
RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT.listen(
builder: (context, box, _) => LunaDialog.checkbox(
title: 'Remove From Client',
value: RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT.data,
onChanged: (selected) => RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT.put(selected),
),
),
RadarrDatabaseValue.QUEUE_BLACKLIST.listen(
builder: (context, box, _) => LunaDialog.checkbox(
title: 'Blacklist Release',
value: RadarrDatabaseValue.QUEUE_BLACKLIST.data,
onChanged: (selected) => RadarrDatabaseValue.QUEUE_BLACKLIST.put(selected),
),
),
],
contentPadding: LunaDialog.listDialogContentPadding(),
);
return _flag;
}
Future<void> setAddTags(BuildContext context) async {
await showDialog(
context: context,

View File

@@ -8,5 +8,6 @@ export 'extensions/radarr_manual_import.dart';
export 'extensions/radarr_movie_file.dart';
export 'extensions/radarr_movie.dart';
export 'extensions/radarr_quality_profile.dart';
export 'extensions/radarr_queue_record.dart';
export 'extensions/radarr_release.dart';
export 'extensions/radarr_system_status.dart';

View File

@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/radarr.dart';
extension LunaRadarrQueueRecord on RadarrQueueRecord {
String get lunaQuality {
return this.quality?.quality?.name ?? LunaUI.TEXT_EMDASH;
}
String get lunaLanguage {
if((this?.languages?.length ?? 0) == 0) return LunaUI.TEXT_EMDASH;
if(this.languages.length == 1) return this.languages[0].name ?? LunaUI.TEXT_EMDASH;
return 'Multi-Language';
}
String lunaTitle(RadarrMovie movie) {
String title = movie?.title ?? LunaUI.TEXT_EMDASH;
String year = movie?.lunaYear;
return '$title ($year)';
}
String get lunaDownloadClient {
if((this?.downloadClient ?? '').isNotEmpty) return this.downloadClient;
return LunaUI.TEXT_EMDASH;
}
String get lunaIndexer {
if((this?.indexer ?? '').isNotEmpty) return this.indexer;
return LunaUI.TEXT_EMDASH;
}
Color get lunaProtocolColor {
if(this.protocol == RadarrProtocol.USENET) return LunaColours.accent;
return LunaColours.blue;
}
int get lunaPercentageComplete {
if(this.sizeLeft == null || this.size == null || this.size == 0) return 0;
double sizeFetched = this.size - this.sizeLeft;
return ((sizeFetched/this.size)*100).round();
}
IconData get lunaStatusIcon {
switch(this.status) {
case RadarrQueueRecordStatus.DELAY: return Icons.access_time_rounded;
case RadarrQueueRecordStatus.DOWNLOAD_CLIENT_UNAVAILABLE: return Icons.access_time_rounded;
case RadarrQueueRecordStatus.FAILED: return Icons.cloud_download_rounded;
case RadarrQueueRecordStatus.PAUSED: return Icons.pause_rounded;
case RadarrQueueRecordStatus.QUEUED: return Icons.cloud_rounded;
case RadarrQueueRecordStatus.WARNING: return Icons.cloud_download_rounded;
case RadarrQueueRecordStatus.COMPLETED: return Icons.download_done_rounded;
case RadarrQueueRecordStatus.DOWNLOADING: return Icons.cloud_download_rounded;
default: return Icons.cloud_download_rounded;
}
}
Color get lunaStatusColor {
Color color = Colors.white;
if(this.status == RadarrQueueRecordStatus.COMPLETED) switch(this.trackedDownloadState) {
case RadarrTrackedDownloadState.FAILED_PENDING: color = LunaColours.red; break;
case RadarrTrackedDownloadState.IMPORT_PENDING: color = LunaColours.purple; break;
case RadarrTrackedDownloadState.IMPORTING: color = LunaColours.purple; break;
default: break;
}
if(this.trackedDownloadStatus == RadarrTrackedDownloadStatus.WARNING) color = LunaColours.orange;
switch(this.status) {
case RadarrQueueRecordStatus.DOWNLOAD_CLIENT_UNAVAILABLE: color = LunaColours.orange; break;
case RadarrQueueRecordStatus.FAILED: color = LunaColours.red; break;
case RadarrQueueRecordStatus.WARNING: color = LunaColours.orange; break;
default: break;
}
if(this.trackedDownloadStatus == RadarrTrackedDownloadStatus.ERROR) color = LunaColours.red;
return color;
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/radarr.dart';
@@ -5,6 +7,12 @@ class RadarrState extends LunaModuleState {
RadarrState() {
reset();
}
@override
void dispose() {
_getQueueTimer?.cancel();
super.dispose();
}
@override
void reset() {
@@ -17,6 +25,7 @@ class RadarrState extends LunaModuleState {
_qualityDefinitions = null;
_languages = null;
_tags = null;
_queue = null;
// Reinitialize
resetProfile();
if(_enabled) {
@@ -26,6 +35,7 @@ class RadarrState extends LunaModuleState {
fetchLanguages();
fetchTags();
fetchMovies();
fetchQueue();
}
notifyListeners();
}
@@ -259,6 +269,38 @@ class RadarrState extends LunaModuleState {
if(_api != null) _tags = _api.tag.getAll();
notifyListeners();
}
/////////////
/// QUEUE ///
/////////////
/// Timer to handle refreshing queue data
Timer _getQueueTimer;
void createQueueTimer() => _getQueueTimer = Timer.periodic(
Duration(seconds: RadarrDatabaseValue.QUEUE_REFRESH_RATE.data),
(_) => fetchQueue(),
);
void cancelQueueTimer() => _getQueueTimer?.cancel();
Future<RadarrQueue> _queue;
Future<RadarrQueue> get queue => _queue;
set queue(Future<RadarrQueue> queue) {
assert(queue != null);
_queue = queue;
notifyListeners();
}
void fetchQueue() {
cancelQueueTimer();
if(_api != null) {
_queue = _api.queue.get();
createQueueTimer();
}
notifyListeners();
}
//////////////
/// IMAGES ///

View File

@@ -42,6 +42,8 @@ class _State extends State<RadarrCatalogueSearchBarFilterButton> {
),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.symmetric(horizontal: 12.0),
color: Theme.of(context).canvasColor,
);

View File

@@ -59,6 +59,8 @@ class _State extends State<RadarrCatalogueSearchBarSortButton> {
),
margin: EdgeInsets.zero,
color: Theme.of(context).canvasColor,
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
);
}
}

View File

@@ -1 +1,2 @@
export 'queue/route.dart';
export 'queue/widgets.dart';

View File

@@ -15,21 +15,79 @@ class _RadarrQueueRoute extends StatefulWidget {
State<StatefulWidget> createState() => _State();
}
class _State extends State<_RadarrQueueRoute> {
class _State extends State<_RadarrQueueRoute> with LunaLoadCallbackMixin, LunaScrollControllerMixin {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@override
Widget build(BuildContext context) {
return LunaScaffold(
scaffoldKey: _scaffoldKey,
appBar: _appBar(),
body: LunaMessage.goBack(context: context, text: 'lunasea.ComingSoon'.tr()),
body: _body(),
);
}
@override
Future<void> loadCallback() async {
if(context.read<RadarrState>().enabled) {
await context.read<RadarrState>().api.command.refreshMonitoredDownloads();
context.read<RadarrState>().fetchQueue();
}
}
Widget _appBar() {
return LunaAppBar(
title: 'radarr.Queue'.tr(),
scrollControllers: [scrollController],
);
}
Widget _body() {
return LunaRefreshIndicator(
key: _refreshKey,
context: context,
onRefresh: loadCallback,
child: FutureBuilder(
future: Future.wait([
context.select((RadarrState state) => state.queue),
context.select((RadarrState state) => state.movies),
]),
builder: (context, AsyncSnapshot<List> snapshot) {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) LunaLogger().error(
'Unable to fetch Radarr queue',
snapshot.error,
snapshot.stackTrace,
);
return LunaMessage.error(onTap: _refreshKey.currentState?.show);
}
if(snapshot.hasData) return _list(snapshot.data[0], snapshot.data[1]);
return LunaLoader();
},
),
);
}
Widget _list(RadarrQueue queue, List<RadarrMovie> movies) {
if((queue?.records?.length ?? 0) == 0) return LunaMessage(
text: 'Empty Queue',
buttonText: 'lunasea.Refresh'.tr(),
onTap: _refreshKey.currentState?.show,
);
return LunaListViewBuilder(
controller: scrollController,
itemCount: queue.records.length,
itemBuilder: (context, index) {
RadarrMovie movie = movies.firstWhere(
(movie) => movie.id == queue.records[index].movieId,
orElse: () => null,
);
return RadarrQueueTile(
record: queue.records[index],
movie: movie,
);
}
);
}
}

View File

@@ -0,0 +1 @@
export 'widgets/queue_tile.dart';

View File

@@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/radarr.dart';
class RadarrQueueTile extends StatelessWidget {
final RadarrQueueRecord record;
final RadarrMovie movie;
RadarrQueueTile({
Key key,
@required this.record,
@required this.movie,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: context.watch<RadarrState>().movies,
builder: (context, AsyncSnapshot<List<RadarrMovie>> snapshot) {
RadarrMovie movie;
if(snapshot.hasData) movie = snapshot.data.firstWhere(
(element) => element.id == record.movieId,
orElse: () => null,
);
return LunaExpandableListTile(
title: record.title,
collapsedSubtitle1: _subtitle1(),
collapsedSubtitle2: _subtitle2(),
expandedHighlightedNodes: _highlightedNodes(),
expandedTableContent: _tableContent(movie),
expandedTableButtons: _tableButtons(context),
collapsedTrailing: LunaIconButton(
icon: record.lunaStatusIcon,
color: record.lunaStatusColor,
),
onLongPress: () async => RadarrMoviesDetailsRouter().navigateTo(context, movieId: record.movieId),
);
},
);
}
TextSpan _subtitle1() {
return TextSpan(text: record?.lunaTitle(movie) ?? LunaUI.TEXT_EMDASH);
}
TextSpan _subtitle2() {
return TextSpan(
children: [
TextSpan(
text: record.lunaQuality,
style: TextStyle(
color: LunaColours.accent,
fontWeight: LunaUI.FONT_WEIGHT_BOLD,
),
),
TextSpan(text: LunaUI.TEXT_BULLET.lunaPad()),
TextSpan(text: record?.timeLeft ?? LunaUI.TEXT_EMDASH),
],
);
}
List<LunaTableContent> _tableContent(RadarrMovie movie) {
return [
LunaTableContent(title: 'radarr.Movie'.tr(), body: record?.lunaTitle(movie)),
LunaTableContent(title: 'radarr.Languages'.tr(), body: record.lunaLanguage),
LunaTableContent(title: 'Client', body: record.lunaDownloadClient),
LunaTableContent(title: 'Indexer', body: record.lunaIndexer),
LunaTableContent(title: 'radarr.Size'.tr(), body: record.size.toInt().lunaBytesToString()),
LunaTableContent(title: 'Time Left', body: record.timeLeft ?? LunaUI.TEXT_EMDASH),
];
}
List<LunaHighlightedNode> _highlightedNodes() {
return [
LunaHighlightedNode(
text: record.protocol?.readable ?? LunaUI.TEXT_EMDASH,
backgroundColor: LunaColours.blue,
),
LunaHighlightedNode(
text: record.lunaQuality,
backgroundColor: LunaColours.accent,
),
if((record.customFormats?.length ?? 0) != 0) for(int i=0; i<record.customFormats.length; i++) LunaHighlightedNode(
text: record.customFormats[i].name,
backgroundColor: LunaColours.orange,
),
LunaHighlightedNode(
text: '${record?.lunaPercentageComplete ?? LunaUI.TEXT_EMDASH}%',
backgroundColor: LunaColours.blueGrey,
),
LunaHighlightedNode(
text: record?.status?.readable ?? LunaUI.TEXT_EMDASH,
backgroundColor: LunaColours.blueGrey,
),
];
}
List<LunaButton> _tableButtons(BuildContext context) {
return [
if((record?.statusMessages ?? []).isNotEmpty) LunaButton.text(
icon: Icons.messenger_outline_rounded,
text: 'Messages',
onTap: () async {
LunaDialogs().showMessages(
context,
record.statusMessages.map<String>((status) => status.messages.join('\n')).toList(),
);
},
),
if(
record?.status == RadarrQueueRecordStatus.COMPLETED &&
record?.trackedDownloadStatus == RadarrTrackedDownloadStatus.WARNING &&
(record?.outputPath ?? '').isNotEmpty
) LunaButton.text(
icon: Icons.download_done_rounded,
text: 'radarr.Import'.tr(),
onTap: () async => RadarrManualImportDetailsRouter().navigateTo(context, path: record.outputPath)
),
LunaButton.text(
icon: Icons.delete_rounded,
color: LunaColours.red,
text: 'Remove',
onTap: () async {
if(context.read<RadarrState>().enabled) {
bool result = await RadarrDialogs().confirmDeleteQueue(context);
if(result) {
await context.read<RadarrState>().api.queue.delete(
id: record.id,
blacklist: RadarrDatabaseValue.QUEUE_BLACKLIST.data,
removeFromClient: RadarrDatabaseValue.QUEUE_REMOVE_FROM_CLIENT.data,
);
context.read<RadarrState>().api.command.refreshMonitoredDownloads()
.then((_) => context.read<RadarrState>().fetchQueue());
}
}
},
),
];
}
}

View File

@@ -44,6 +44,8 @@ class _State extends State<RadarrReleasesAppBarFilterButton> {
),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.fromLTRB(0.0, 0.0, 12.0, 14.0),
color: Theme.of(context).canvasColor,
);

View File

@@ -61,6 +61,8 @@ class _State extends State<RadarrReleasesAppBarSortButton> {
),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.fromLTRB(0.0, 0.0, 12.0, 13.5),
color: Theme.of(context).canvasColor,
);

View File

@@ -26,6 +26,8 @@ class _State extends State<SABnzbdHistoryHideButton> {
onPressed: () => model.historyHideFailed = !model.historyHideFailed,
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.only(left: 12.0),
color: Theme.of(context).canvasColor,
);

View File

@@ -70,7 +70,7 @@ class _State extends State<_SettingsConfigurationRoute> with LunaScrollControlle
},
),
),
LunaListTile(
if(LunaQuickActions.isPlatformCompatible) LunaListTile(
context: context,
title: LunaText.title(text: 'Quick Actions'),
subtitle: LunaText.subtitle(text: 'Quick Actions on the Home Screen'),

View File

@@ -64,7 +64,7 @@ class _State extends State<_SettingsHomeRoute> with LunaScrollControllerMixin {
onTap: () async => SettingsProfilesRouter().navigateTo(context),
),
LunaDivider(),
LunaListTile(
if(LunaInAppPurchases.isPlatformCompatible) LunaListTile(
context: context,
title: LunaText.title(text: 'Donations'),
subtitle: LunaText.subtitle(text: 'Donate to the Developer'),

View File

@@ -2,7 +2,7 @@ import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/settings.dart';
import 'package:package_info/package_info.dart';
import 'package:package_info_plus/package_info_plus.dart';
class SettingsSystemRouter extends SettingsPageRouter {
SettingsSystemRouter() : super('/settings/system');
@@ -45,9 +45,9 @@ class _State extends State<_SettingsSystemRoute> with LunaScrollControllerMixin
SettingsSystemBackupRestoreBackupTile(),
SettingsSystemBackupRestoreRestoreTile(),
LunaDivider(),
_enableAnalytics(),
_enableCrashlytics(),
LunaDivider(),
if(LunaFirebaseAnalytics.isPlatformCompatible) _enableAnalytics(),
if(LunaFirebaseCrashlytics.isPlatformCompatible) _enableCrashlytics(),
if(LunaFirebaseCrashlytics.isPlatformCompatible || LunaFirebaseAnalytics.isPlatformCompatible) LunaDivider(),
_language(),
_clearConfiguration(),
],
@@ -57,17 +57,18 @@ class _State extends State<_SettingsSystemRoute> with LunaScrollControllerMixin
Widget _versionInformation() {
return FutureBuilder(
future: PackageInfo.fromPlatform(),
builder: (context, AsyncSnapshot<PackageInfo> snapshot) => LunaListTile(
context: context,
title: LunaText.title(
text: snapshot.hasData
? 'Version: ${snapshot.data.version} (${snapshot.data.buildNumber})'
: 'Version: Loading...',
),
subtitle: LunaText.subtitle(text: 'View Recent Changes'),
trailing: LunaIconButton(icon: Icons.system_update),
onTap: () async => LunaChangelog().showChangelog(snapshot.data.version, snapshot.data.buildNumber),
),
builder: (context, AsyncSnapshot<PackageInfo> snapshot) {
String version = 'Loading${LunaUI.TEXT_ELLIPSIS}';
if(snapshot.hasError) version = 'Unknown';
if(snapshot.hasData) version = '${snapshot.data.version} (${snapshot.data.buildNumber})';
return LunaListTile(
context: context,
title: LunaText.title(text: 'Version: $version'),
subtitle: LunaText.subtitle(text: 'View Recent Changes'),
trailing: LunaIconButton(icon: Icons.system_update),
onTap: () async => LunaChangelog().showChangelog(snapshot.data?.version, snapshot.data?.buildNumber),
);
}
);
}

View File

@@ -91,7 +91,7 @@ class _State extends State<SonarrQueueQueueTile> {
children: [
LunaHighlightedNode(
text: widget.record?.protocol?.lunaCapitalizeFirstLetters() ?? LunaUI.TEXT_EMDASH,
backgroundColor: widget.record.protocol == 'torrent' ? LunaColours.purple : LunaColours.blue,
backgroundColor: LunaColours.blue,
),
LunaHighlightedNode(
text: widget.record?.quality?.quality?.name ?? 'Unknown',
@@ -167,15 +167,14 @@ class _State extends State<SonarrQueueQueueTile> {
text: 'Messages',
icon: Icons.messenger_outline_rounded,
onTap: () async {
String messages = widget.record.statusMessages.fold('', (warnings, status) {
warnings += status.messages.fold<String>('', (message, element) => message += '\n${LunaUI.TEXT_BULLET} $element');
return warnings.trim();
});
LunaDialogs().textPreview(context, 'Messages', messages);
LunaDialogs().showMessages(
context,
widget.record.statusMessages.map<String>((status) => status.messages.join('\n')).toList(),
);
},
),
LunaButton.text(
text: 'Delete',
text: 'Remove',
icon: Icons.delete_rounded,
color: LunaColours.red,
onTap: () async => _deleteQueueRecord(),

View File

@@ -43,6 +43,8 @@ class _State extends State<SonarrReleasesAppBarFilterButton> {
),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: LunaTextInputBar.appBarMargin.subtract(EdgeInsets.only(left: 12.0)),
color: Theme.of(context).canvasColor,
);

View File

@@ -60,6 +60,8 @@ class _State extends State<SonarrReleasesAppBarSortButton> {
),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: LunaTextInputBar.appBarMargin.subtract(EdgeInsets.only(left: 12.0)),
color: Theme.of(context).canvasColor,
);

View File

@@ -43,6 +43,8 @@ class _State extends State<SonarrSeriesSearchBarFilterButton> {
),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.symmetric(horizontal: 12.0),
color: Theme.of(context).canvasColor,
);

View File

@@ -60,6 +60,8 @@ class _State extends State<SonarrSeriesSearchBarSortButton> {
),
),
),
height: LunaTextInputBar.appBarInnerHeight,
width: LunaTextInputBar.appBarInnerHeight,
margin: EdgeInsets.zero,
color: Theme.of(context).canvasColor,
);

View File

@@ -33,6 +33,7 @@
"radarr.InCinemasIn": "In Cinemas in {}",
"radarr.InCinemasToday": "In Cinemas Today",
"radarr.Interactive": "Interactive",
"radarr.Language": "Language",
"radarr.Languages": "Languages",
"radarr.ManualImport": "Manual Import",
"radarr.ManualImportDescription": "Import Movies from the Filesystem",

View File

@@ -13,7 +13,7 @@ import firebase_crashlytics
import firebase_messaging
import firebase_storage
import network_info_plus_macos
import package_info
import package_info_plus_macos
import path_provider_macos
import shared_preferences_macos
import url_launcher_macos

View File

@@ -243,7 +243,7 @@ PODS:
- Firebase/Storage (7.3.0):
- Firebase/CoreOnly
- FirebaseStorage (~> 7.3.0)
- firebase_auth (1.0.2):
- firebase_auth (1.0.3):
- Firebase/Auth (~> 7.3.0)
- Firebase/CoreOnly (~> 7.3.0)
- firebase_core
@@ -261,7 +261,7 @@ PODS:
- Firebase/Messaging (~> 7.3.0)
- firebase_core
- FlutterMacOS
- firebase_storage (8.0.2):
- firebase_storage (8.0.3):
- Firebase/CoreOnly (~> 7.3.0)
- Firebase/Storage (~> 7.3.0)
- firebase_core
@@ -372,7 +372,7 @@ PODS:
- network_info_plus_macos (0.0.1):
- FlutterMacOS
- Reachability
- package_info (0.0.1):
- package_info_plus_macos (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1):
- FlutterMacOS
@@ -393,7 +393,7 @@ DEPENDENCIES:
- firebase_storage (from `Flutter/ephemeral/.symlinks/plugins/firebase_storage/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- network_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/network_info_plus_macos/macos`)
- package_info (from `Flutter/ephemeral/.symlinks/plugins/package_info/macos`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
@@ -441,8 +441,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral
network_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/network_info_plus_macos/macos
package_info:
:path: Flutter/ephemeral/.symlinks/plugins/package_info/macos
package_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
shared_preferences_macos:
@@ -456,11 +456,11 @@ SPEC CHECKSUMS:
cloud_firestore: 2f06cee99ba40ed1ed79792bafa13e5727bcb706
connectivity_plus_macos: dbac1dc9b113dfb69a5691831313b1d8ac1c8269
Firebase: 26223c695fe322633274198cb19dca8cb7e54416
firebase_auth: a17717c1f074d844ed176534dd588537293d7b27
firebase_auth: ba042208860f086aead3c50fcacff096262854e8
firebase_core: 5c0a84649819db6738f6d159548762a2def9cb50
firebase_crashlytics: 748de8459d6b018d3cca37e6ed7f8c2fbd86d4cd
firebase_messaging: 044c01f581b49f873a217792b3e2006ba7be8b9f
firebase_storage: 42cc28b760954e1a2810565ad0ffccfcd3b3fa49
firebase_storage: e0b723ec5030607a97de3f269c6bb7145de3d193
FirebaseAuth: c224a0cf1afa0949bd5c7bfcf154b4f5ce8ddef2
FirebaseCore: 4d3c72622ce0e2106aaa07bb4b2935ba2c370972
FirebaseCoreDiagnostics: d50e11039e5984d92c8a512be2395f13df747350
@@ -479,7 +479,7 @@ SPEC CHECKSUMS:
leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729
nanopb: 1bf24dd71191072e120b83dd02d08f3da0d65e53
network_info_plus_macos: 38ec4a578ecdfcddab609f61da3026d0b32900e5
package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96

View File

@@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "19.0.0"
version: "20.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.4.0"
archive:
dependency: transitive
description:
@@ -343,21 +343,21 @@ packages:
name: firebase_auth
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.0.3"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
version: "4.0.2"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
version: "1.0.5"
firebase_core:
dependency: "direct main"
description:
@@ -420,7 +420,7 @@ packages:
name: firebase_storage
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.2"
version: "8.0.3"
firebase_storage_platform_interface:
dependency: transitive
description:
@@ -582,7 +582,7 @@ packages:
name: in_app_purchase
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1+2"
version: "0.5.1+3"
infinite_scroll_pagination:
dependency: "direct main"
description:
@@ -709,13 +709,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
package_info:
package_info_plus:
dependency: "direct main"
description:
name: package_info
name: package_info_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "1.0.0"
package_info_plus_linux:
dependency: transitive
description:
name: package_info_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
package_info_plus_macos:
dependency: transitive
description:
name: package_info_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
package_info_plus_web:
dependency: transitive
description:
name: package_info_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
package_info_plus_windows:
dependency: transitive
description:
name: package_info_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
path:
dependency: transitive
description:
@@ -855,14 +890,14 @@ packages:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.0.1"
radarr:
dependency: "direct main"
description:
name: radarr
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
path: "/Users/jagandeepbrar/Git/Packages/radarr"
relative: false
source: path
version: "2.1.0"
share:
dependency: "direct main"
description:
@@ -1098,7 +1133,7 @@ packages:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.3"
version: "3.0.4"
vector_math:
dependency: transitive
description:

View File

@@ -13,36 +13,38 @@ dependencies:
connectivity_plus: ^1.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
convert: ^3.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
dio: ^4.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
easy_localization: ^3.0.0 # ANDROID IOS LINUX MACOS WEB
easy_localization: ^3.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
encrypt: ^5.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
expandable: ^5.0.1 # ANDROID IOS LINUX MACOS WEB WINDOWS
file_picker: ^3.0.1 # ANDROID IOS WEB
firebase_analytics: ^7.1.1 # ANDROID IOS WEB
firebase_auth: ^1.0.2 # ANDROID IOS MACOS WEB
firebase_auth: ^1.0.3 # ANDROID IOS MACOS WEB
firebase_core: ^1.0.3 # ANDROID IOS MACOS WEB
firebase_crashlytics: ^2.0.0 # ANDROID IOS MACOS
firebase_messaging: ^9.1.1 # ANDROID IOS MACOS WEB
firebase_storage: ^8.0.2 # ANDROID IOS MACOS WEB
firebase_storage: ^8.0.3 # ANDROID IOS MACOS WEB
flash: ^1.5.1 # ANDROID IOS LINUX MACOS WEB WINDOWS
fluro: ^2.0.3 # ANDROID IOS LINUX MACOS WEB WINDOWS
flutter_spinkit: ^5.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
fl_chart: ^0.35.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
# font_awesome_flutter: # ANDROID IOS LINUX MACOS WEB WINDOWS
# path: vendor/font_awesome_flutter
google_nav_bar: ^5.0.3 # ANDROID IOS LINUX MACOS WEB WINDOWS
hive: ^2.0.2 # ANDROID IOS LINUX MACOS WEB WINDOWS
hive_flutter: ^1.0.0 # ANDROID IOS LINUX MACOS WINDOWS
in_app_purchase: ^0.5.1+2 # ANDROID IOS
in_app_purchase: ^0.5.1+3 # ANDROID IOS
infinite_scroll_pagination: ^3.0.1 # ANDROID IOS LINUX MACOS WEB WINDOWS
intl: ^0.17.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
modal_bottom_sheet: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
network_info_plus: ^1.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
package_info: ^2.0.0 # ANDROID IOS MACOS
package_info_plus: ^1.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
path_provider: ^2.0.1 # ANDROID IOS LINUX MACOS WINDOWS
percent_indicator: ^3.0.1 # ANDROID IOS LINUX MACOS WEB WINDOWS
provider: ^5.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
quick_actions: ^0.6.0 # ANDROID IOS
radarr: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
# radarr:
# path: /Users/jagandeepbrar/Git/Packages/radarr
# radarr: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
radarr:
path: /Users/jagandeepbrar/Git/Packages/radarr
share: ^2.0.1 # ANDROID IOS
sonarr: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
stack_trace: ^1.10.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
@@ -51,7 +53,7 @@ dependencies:
tautulli: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
tuple: ^2.0.0 # ANDROID IOS LINUX MACOS WEB WINDOWS
url_launcher: ^6.0.3 # ANDROID IOS LINUX MACOS WEB WINDOWS
uuid: ^3.0.3 # ANDROID IOS LINUX MACOS WEB WINDOWS
uuid: ^3.0.4 # ANDROID IOS LINUX MACOS WEB WINDOWS
wake_on_lan: ^2.0.1+1 # ANDROID IOS LINUX MACOS WINDOWS
xml: ^5.1.0 # ANDROID IOS LINUX MACOS WEB WINDOWS

12
vendor/font_awesome_flutter/.gitignore vendored Executable file
View File

@@ -0,0 +1,12 @@
.DS_Store
.atom/
.idea
.packages
.dart_tool/
.pub/
build/
ios/.generated/
packages
pubspec.lock
.flutter-plugins
local.properties

23
vendor/font_awesome_flutter/LICENSE vendored Normal file
View File

@@ -0,0 +1,23 @@
MIT License
Copyright (c) 2017 Brian Egan
Font Awesome Icons by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,71 @@
import 'package:flutter/widgets.dart';
import 'package:font_awesome_flutter/src/fa_icon.dart';
import 'package:font_awesome_flutter/src/icon_data.dart';
/// Special icon style which can have two colors
class FaDuotoneIcon extends StatelessWidget {
/// The icon to display. The available icons are described in
/// [FontAwesomeIcons]. Applicable icons start with "duotone".
final IconDataDuotone icon;
/// The font size of the icon.
///
/// Defaults to the current [IconTheme] size, if any. If there is no
/// [IconTheme], or it does not specify an explicit size, then it defaults to
/// 24.0.
///
/// If this [FaIcon] is being placed inside an [IconButton], then use
/// [IconButton.iconSize] instead, so that the [IconButton] can make the
/// splash area the appropriate size as well. The [IconButton] uses an
/// [IconTheme] to pass down the size to the [FaIcon].
final double? size;
/// Color used for the icon's main body
final Color? primaryColor;
/// Color used for the icon's accents
final Color? secondaryColor;
/// Semantic label for the icon.
///
/// Announced in accessibility modes (e.g TalkBack/VoiceOver).
/// This label does not show in the UI.
///
/// See also:
///
/// * [Semantics.label], which is set to [semanticLabel] in the underlying
/// [Semantics] widget.
final String? semanticLabel;
const FaDuotoneIcon(
this.icon, {
Key? key,
this.size,
this.primaryColor,
this.secondaryColor,
this.semanticLabel,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Semantics(
label: semanticLabel,
child: Stack(
children: <Widget>[
FaIcon(
icon,
size: size,
color: primaryColor,
key: Key("primaryGlyph"),
),
FaIcon(
icon.secondary,
size: size,
color: secondaryColor,
key: Key("secondaryGlyph"),
),
],
),
);
}
}

View File

@@ -0,0 +1,148 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
/// Creates an Icon Widget that works for non-material Icons, such as the
/// Font Awesome Icons.
///
/// The default `Icon` Widget from the Material package assumes all Icons are
/// square in size and wraps all Icons in a square SizedBox Widget. Icons from
/// the FontAwesome package are often wider than they are tall, which causes
/// alignment and cutoff issues.
///
/// This Widget does not wrap the icons in a fixed square box, which allows the
/// icons to render based on their size.
class FaIcon extends StatelessWidget {
/// Creates an icon.
///
/// The [size] and [color] default to the value given by the current [IconTheme].
const FaIcon(
this.icon, {
Key? key,
this.size,
this.color,
this.semanticLabel,
this.textDirection,
}) : assert(icon != null),
super(key: key);
/// The icon to display. The available icons are described in
/// [FontAwesomeIcons].
final IconData? icon;
/// The font size of the icon.
///
/// Defaults to the current [IconTheme] size, if any. If there is no
/// [IconTheme], or it does not specify an explicit size, then it defaults to
/// 24.0.
///
/// If this [FaIcon] is being placed inside an [IconButton], then use
/// [IconButton.iconSize] instead, so that the [IconButton] can make the
/// splash area the appropriate size as well. The [IconButton] uses an
/// [IconTheme] to pass down the size to the [FaIcon].
final double? size;
/// The color to use when drawing the icon.
///
/// Defaults to the current [IconTheme] color, if any.
///
/// The given color will be adjusted by the opacity of the current
/// [IconTheme], if any.
final Color? color;
/// Semantic label for the icon.
///
/// Announced in accessibility modes (e.g TalkBack/VoiceOver).
/// This label does not show in the UI.
///
/// See also:
///
/// * [Semantics.label], which is set to [semanticLabel] in the underlying
/// [Semantics] widget.
final String? semanticLabel;
/// The text direction to use for rendering the icon.
///
/// If this is null, the ambient [Directionality] is used instead.
///
/// Some icons follow the reading direction. For example, "back" buttons point
/// left in left-to-right environments and right in right-to-left
/// environments. Such icons have their [IconData.matchTextDirection] field
/// set to true, and the [FaIcon] widget uses the [textDirection] to determine
/// the orientation in which to draw the icon.
///
/// This property has no effect if the [icon]'s [IconData.matchTextDirection]
/// field is false, but for consistency a text direction value must always be
/// specified, either directly using this property or using [Directionality].
final TextDirection? textDirection;
@override
Widget build(BuildContext context) {
assert(this.textDirection != null || debugCheckHasDirectionality(context));
final TextDirection textDirection =
this.textDirection ?? Directionality.of(context);
final IconThemeData iconTheme = IconTheme.of(context);
final double? iconSize = size ?? iconTheme.size;
if (icon == null) {
return Semantics(
label: semanticLabel,
child: SizedBox(width: iconSize, height: iconSize),
);
}
final double iconOpacity = iconTheme.opacity ?? 1.0;
Color iconColor = color ?? iconTheme.color!;
if (iconOpacity != 1.0)
iconColor = iconColor.withOpacity(iconColor.opacity * iconOpacity);
Widget iconWidget = RichText(
overflow: TextOverflow.visible, // Never clip.
textDirection:
textDirection, // Since we already fetched it for the assert...
text: TextSpan(
text: String.fromCharCode(icon!.codePoint),
style: TextStyle(
inherit: false,
color: iconColor,
fontSize: iconSize,
fontFamily: icon!.fontFamily,
package: icon!.fontPackage,
),
),
);
if (icon!.matchTextDirection) {
switch (textDirection) {
case TextDirection.rtl:
iconWidget = Transform(
transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
alignment: Alignment.center,
transformHitTests: false,
child: iconWidget,
);
break;
case TextDirection.ltr:
break;
}
}
return Semantics(
label: semanticLabel,
child: ExcludeSemantics(
child: iconWidget,
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
IconDataProperty('icon', icon, ifNull: '<empty>', showName: false));
properties.add(DoubleProperty('size', size, defaultValue: null));
properties.add(ColorProperty('color', color, defaultValue: null));
}
}

View File

@@ -0,0 +1,85 @@
library font_awesome_flutter;
import 'package:flutter/widgets.dart';
/// [IconData] for a font awesome brand icon from a code point
///
/// Code points can be obtained from fontawesome.com
class IconDataBrands extends IconData {
const IconDataBrands(int codePoint)
: super(
codePoint,
fontFamily: 'FontAwesomeBrands',
fontPackage: 'font_awesome_flutter',
);
}
/// [IconData] for a font awesome solid icon from a code point
///
/// Code points can be obtained from fontawesome.com
class IconDataSolid extends IconData {
const IconDataSolid(int codePoint)
: super(
codePoint,
fontFamily: 'FontAwesomeSolid',
fontPackage: 'font_awesome_flutter',
);
}
/// [IconData] for a font awesome regular icon from a code point
///
/// Code points can be obtained from fontawesome.com
class IconDataRegular extends IconData {
const IconDataRegular(int codePoint)
: super(
codePoint,
fontFamily: 'FontAwesomeRegular',
fontPackage: 'font_awesome_flutter',
);
}
/// [IconData] for a font awesome light icon from a code point. Only works if
/// light icons (font awesome pro) have been installed.
///
/// Code points can be obtained from fontawesome.com
class IconDataLight extends IconData {
const IconDataLight(int codePoint)
: super(
codePoint,
fontFamily: 'FontAwesomeLight',
fontPackage: 'font_awesome_flutter',
);
}
/// [IconData] for a font awesome duotone icon from a code point. Only works if
/// duotone icons (font awesome pro) have been installed.
///
/// Code points can be obtained from fontawesome.com. Each duotone icon consists
/// of a primary [codePoint] and a [secondary].
class IconDataDuotone extends IconData {
/// Secondary glyph of the duotone icon
///
/// Due to tree-shaking restraints [secondary] cannot be the codepoint itself,
/// but has to be an [IconData] object.
final IconData? secondary;
const IconDataDuotone(int codePoint, {this.secondary})
: super(
codePoint,
fontFamily: 'FontAwesomeDuotone',
fontPackage: 'font_awesome_flutter',
);
}
/// [IconData] for a font awesome thin icon from a code point. Only works if
/// thin icons (font awesome pro, v6+) have been installed.
///
/// Code points can be obtained from fontawesome.com
class IconDataThin extends IconData {
const IconDataThin(int codePoint)
: super(
codePoint,
fontFamily: 'FontAwesomeThin',
fontPackage: 'font_awesome_flutter',
);
}

49
vendor/font_awesome_flutter/pubspec.yaml vendored Executable file
View File

@@ -0,0 +1,49 @@
name: font_awesome_flutter
description: The Font Awesome Icon pack available as Flutter Icons. Provides 1500 additional icons to use in your apps.
maintainer: Michael Spiss (@michaelspiss)
homepage: https://github.com/fluttercommunity/font_awesome_flutter
version: 9.0.0
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
recase: ^4.0.0-nullsafety
flutter_test:
sdk: flutter
version: ^2.0.0
flutter:
fonts:
- family: FontAwesomeBrands
fonts:
- asset: lib/fonts/fa-brands-400.ttf
weight: 400
- family: FontAwesomeRegular
fonts:
- asset: lib/fonts/fa-regular-400.ttf
weight: 400
- family: FontAwesomeSolid
fonts:
- asset: lib/fonts/fa-solid-900.ttf
weight: 900
# Uncomment the following lines to support pro icons
- family: FontAwesomeLight
fonts:
- asset: lib/fonts/fa-light-300.ttf
weight: 300
# Leave commented out if you don't own duotone icons
- family: FontAwesomeDuotone
fonts:
- asset: lib/fonts/fa-duotone-900.ttf
weight: 900
# Thin style is available in font awesome pro 6 and beyond
# Leave commented out if you don't own version 6
- family: FontAwesomeThin
fonts:
- asset: lib/fonts/fa-thin-100.ttf
weight: 100

View File

@@ -0,0 +1,87 @@
import 'dart:convert';
import 'dart:io';
import 'package:recase/recase.dart';
const Map<String, String> nameAdjustments = {
"500px": "fiveHundredPx",
"360-degrees": "threeHundredSixtyDegrees",
"1": "one",
"2": "two",
"3": "three",
"4": "four",
"5": "five",
"6": "six",
"7": "seven",
"8": "eight",
"9": "nine",
"0": "zero",
};
void main(List<String> arguments) {
var file = new File(arguments.first);
if (!file.existsSync()) {
print('Cannot find the file "${arguments.first}".');
}
var content = file.readAsStringSync();
Map<String, dynamic> icons = json.decode(content);
Map<String, String> iconDefinitions = {};
for (String iconName in icons.keys) {
var icon = icons[iconName];
// At least one icon does not have a glyph in the font files. This property
// is marked with "private": true in icons.json
if ((icon as Map<String, dynamic>).containsKey('private') &&
icon['private']) continue;
List<String> styles = (icon['styles'] as List).cast<String>();
if (styles.length > 1) {
if (styles.contains('regular')) {
styles.remove('regular');
iconDefinitions[iconName] = generateExampleIcon(iconName);
}
for (String style in styles) {
String name = '${style}_$iconName';
iconDefinitions[name] = generateExampleIcon(name);
}
} else {
iconDefinitions[iconName] = generateExampleIcon(iconName);
}
}
List<String> generatedOutput = [
'',
"import 'package:font_awesome_flutter/font_awesome_flutter.dart';",
"import 'package:font_awesome_flutter_example/example_icon.dart';",
'',
'// THIS FILE IS AUTOMATICALLY GENERATED!',
'',
'final icons = <ExampleIcon>[',
];
generatedOutput.addAll(iconDefinitions.values);
generatedOutput.add('];');
File output = new File('example/lib/icons.dart');
output.writeAsStringSync(generatedOutput.join('\n'));
}
String generateExampleIcon(String iconName) {
if (nameAdjustments.containsKey(iconName)) {
iconName = nameAdjustments[iconName]!;
}
iconName = new ReCase(iconName).camelCase;
if (iconName.contains('duotone')) {
return " ExampleIcon(null, FontAwesomeIcons.$iconName, '$iconName'),";
} else {
return " ExampleIcon(FontAwesomeIcons.$iconName, null, '$iconName'),";
}
}

View File

@@ -0,0 +1,204 @@
import 'dart:convert';
import 'dart:io';
import 'package:recase/recase.dart';
import 'package:version/version.dart';
const Map<String, String> nameAdjustments = {
"500px": "fiveHundredPx",
"360-degrees": "threeHundredSixtyDegrees",
"1": "one",
"2": "two",
"3": "three",
"4": "four",
"5": "five",
"6": "six",
"7": "seven",
"8": "eight",
"9": "nine",
"0": "zero",
};
void main(List<String> arguments) {
var file = new File(arguments.first);
if (!file.existsSync()) {
print('Cannot find the file "${arguments.first}".');
}
var content = file.readAsStringSync();
Map<String, dynamic> icons = json.decode(content);
Map<String, String> iconDefinitions = {};
bool hasDuotone = false;
// figure out current version
Version highestVersion = Version.parse("0.0.0");
for (String iconName in icons.keys) {
var icon = icons[iconName];
// At least one icon does not have a glyph in the font files. This property
// is marked with "private": true in icons.json
if ((icon as Map<String, dynamic>).containsKey('private') &&
icon['private']) continue;
// compute highest version
var changes = (icon["changes"] as List);
List<Version> versions = changes.map((v) {
// since font awesome does not adhere to semver standards
var partsCount = v.split(".").length;
if (partsCount == 1) {
return v + ".0.0";
} else if (partsCount == 2) {
return v + ".0";
} else if (partsCount == 3) {
return v;
} else {
return "0.0.0";
}
}).map((v) {
try {
return Version.parse(v);
} on FormatException {
return Version.parse("0.0.0");
}
}).toList(growable: true)
..add(highestVersion)
..sort();
highestVersion = versions.last;
var unicode = icon['unicode'];
List<String> styles = (icon['styles'] as List).cast<String>();
if (styles.length > 1) {
if (styles.contains('regular')) {
styles.remove('regular');
iconDefinitions[iconName] = generateIconDefinition(
iconName,
'regular',
unicode,
icon["search"]["terms"],
icon["label"],
);
}
if (styles.contains('duotone')) {
hasDuotone = true;
}
for (String style in styles) {
String name = '${style}_$iconName';
iconDefinitions[name] = generateIconDefinition(
name,
style,
unicode,
icon["search"]["terms"],
icon["label"],
);
}
} else {
iconDefinitions[iconName] = generateIconDefinition(
iconName,
styles.first,
unicode,
icon["search"]["terms"],
icon["label"],
);
}
}
List<String> generatedOutput = [
'library font_awesome_flutter;',
'',
"import 'package:flutter/widgets.dart';",
"import 'package:font_awesome_flutter/src/icon_data.dart';",
"export 'package:font_awesome_flutter/src/fa_icon.dart';",
"export 'package:font_awesome_flutter/src/icon_data.dart';",
"export 'package:font_awesome_flutter/src/fa_duotone_icon.dart';",
];
if (hasDuotone) {
generatedOutput
.add("export 'package:font_awesome_flutter/src/fa_duotone_icon.dart';");
}
generatedOutput.addAll([
'',
'// THIS FILE IS AUTOMATICALLY GENERATED!',
'',
'/// Icons based on font awesome $highestVersion',
'class FontAwesomeIcons {',
]);
generatedOutput.addAll(iconDefinitions.values);
generatedOutput.add('}');
File output = new File('lib/font_awesome_flutter.dart');
output.writeAsStringSync(generatedOutput.join('\n'));
}
String generateIconDocumentation(
String iconName, String style, List searchTerms, String iconLabel) {
searchTerms = searchTerms;
var searchTermsString = searchTerms.toString();
searchTermsString =
searchTermsString.substring(1, searchTermsString.length - 1);
iconName = iconName.replaceFirst("solid_", "");
var doc = '/// ${style.sentenceCase} $iconLabel icon\n'
'///\n'
'/// https://fontawesome.com/icons/$iconName?style=$style';
if (searchTermsString.length != 0) {
doc += '\n/// $searchTermsString';
}
return doc;
}
String generateIconDefinition(String iconName, String style, String unicode,
List searchTerms, String iconLabel) {
if (style == 'duotone') {
return generateDuotoneIconDefinition(
iconName, unicode, searchTerms, iconLabel);
}
String doc =
generateIconDocumentation(iconName, style, searchTerms, iconLabel);
iconName = normalizeIconName(iconName);
String iconDataSource = styleToDataSource(style);
return '$doc\nstatic const IconData $iconName = const $iconDataSource(0x$unicode);';
}
String generateDuotoneIconDefinition(String iconName, String primaryUnicode,
List searchTerms, String iconLabel) {
String doc =
generateIconDocumentation(iconName, "duotone", searchTerms, iconLabel);
iconName = normalizeIconName(iconName);
String secondaryUnicode = (int.parse(primaryUnicode, radix: 16) + 0x100000)
.toRadixString(16)
.toString();
return '$doc\nstatic const IconDataDuotone $iconName = const IconDataDuotone(0x$primaryUnicode, secondary: const IconDataDuotone(0x$secondaryUnicode),);';
}
String normalizeIconName(String iconName) {
iconName = nameAdjustments[iconName] ?? iconName;
return new ReCase(iconName).camelCase;
}
String styleToDataSource(String style) {
style = '${style[0].toUpperCase()}${style.substring(1)}';
return 'IconData$style';
}

View File

@@ -0,0 +1,25 @@
@echo off
if exist icons.json (
echo Custom icons.json found, using local data only.
dart .\tool\generate_font.dart .\icons.json
dart .\tool\generate_example.dart .\icons.json
) else (
echo Updating free icons to newest version.
pushd lib\fonts
powershell -Command "(New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/webfonts/fa-brands-400.ttf', 'fa-brands-400.ttf')"
powershell -Command "(New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/webfonts/fa-regular-400.ttf', 'fa-regular-400.ttf')"
powershell -Command "(New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/webfonts/fa-solid-900.ttf', 'fa-solid-900.ttf')"
popd
powershell -Command "(New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/metadata/icons.json', 'icons.json')"
dart .\tool\generate_font.dart .\icons.json
dart .\tool\generate_example.dart .\icons.json
del "icons.json" /f /q
)
dartfmt -w .\lib\font_awesome_flutter.dart & ^
dartfmt -w .\example\lib\icons.dart

29
vendor/font_awesome_flutter/tool/update.sh vendored Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
if [ -e ./icons.json ];
then
echo "Custom icons.json found, using local data only."
dart ./tool/generate_font.dart ./icons.json
dart ./tool/generate_example.dart ./icons.json
dartfmt -w ./lib/font_awesome_flutter.dart
dartfmt -w ./example/lib/icons.dart
else
echo "Updating icons to newest version."
pushd lib/fonts/
curl -O -L "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/webfonts/fa-brands-400.ttf"
curl -O -L "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/webfonts/fa-regular-400.ttf"
curl -O -L "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/webfonts/fa-solid-900.ttf"
popd
curl -o /tmp/icons.json "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/metadata/icons.json"
dart ./tool/generate_font.dart /tmp/icons.json
dart ./tool/generate_example.dart /tmp/icons.json
dartfmt -w ./lib/font_awesome_flutter.dart
dartfmt -w ./example/lib/icons.dart
rm /tmp/icons.json
fi