Add CTA to Chrome task extension
This commit is contained in:
parent
2fb7945f23
commit
267fe96644
|
|
@ -53,6 +53,11 @@ const String kFacebookUrl = 'https://www.facebook.com/invoiceninja';
|
|||
const String kYouTubeUrl =
|
||||
'https://www.youtube.com/channel/UCXAHcBvhW05PDtWYIq7WDFA/videos';
|
||||
|
||||
const String kTaskExtensionUrl =
|
||||
'https://chromewebstore.google.com/detail/invoice-ninja-tasks/dlfcbfdpemfnjbjlladogijcchfmmaaf';
|
||||
const String kTaskExtensionYouTubeUrl =
|
||||
'https://www.youtube.com/watch?v=UL0OklMJTEA&ab_channel=InvoiceNinja';
|
||||
|
||||
const String kAppleOAuthClientId = 'com.invoiceninja.client';
|
||||
const String kAppleOAuthRedirectUrl = 'https://invoicing.co/auth/apple';
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ class DismissOneYearReviewAppPermanently implements PersistUI, PersistPrefs {}
|
|||
|
||||
class DismissTwoYearReviewAppPermanently implements PersistUI, PersistPrefs {}
|
||||
|
||||
class DismissTaskExtensionBanner implements PersistUI, PersistPrefs {}
|
||||
|
||||
class ViewMainScreen {
|
||||
ViewMainScreen({this.addDelay = false});
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ PrefState prefReducer(
|
|||
historySidebarReducer(state.historySidebarMode, action)
|
||||
..hideDesktopWarning =
|
||||
hideDesktopWarningReducer(state.hideDesktopWarning, action)
|
||||
..hideTaskExtensionBanner =
|
||||
hideTaskExtensionBannerReducer(state.hideTaskExtensionBanner, action)
|
||||
..hideGatewayWarning =
|
||||
hideGatewayWarningReducer(state.hideGatewayWarning, action)
|
||||
..hideReviewApp = hideReviewAppReducer(state.hideReviewApp, action)
|
||||
|
|
@ -260,6 +262,12 @@ Reducer<bool> hideDesktopWarningReducer = combineReducers([
|
|||
}),
|
||||
]);
|
||||
|
||||
Reducer<bool> hideTaskExtensionBannerReducer = combineReducers([
|
||||
TypedReducer<bool, DismissTaskExtensionBanner>((filter, action) {
|
||||
return true;
|
||||
}),
|
||||
]);
|
||||
|
||||
Reducer<bool> hideGatewayWarningReducer = combineReducers([
|
||||
TypedReducer<bool, DismissGatewayWarningPermanently>((filter, action) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
hideReviewApp: false,
|
||||
hideOneYearReviewApp: false,
|
||||
hideTwoYearReviewApp: false,
|
||||
hideTaskExtensionBanner: false,
|
||||
showKanban: false,
|
||||
showPdfPreview: true,
|
||||
showPdfPreviewSideBySide: false,
|
||||
|
|
@ -170,6 +171,8 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
|
||||
bool get hideTwoYearReviewApp;
|
||||
|
||||
bool get hideTaskExtensionBanner;
|
||||
|
||||
bool get editAfterSaving;
|
||||
|
||||
bool get enableNativeBrowser;
|
||||
|
|
@ -288,7 +291,8 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
..colorTheme = kColorThemeLight
|
||||
..darkColorTheme = kColorThemeDark
|
||||
..enableDarkModeSystem = false
|
||||
..donwloadsFolder = '';
|
||||
..donwloadsFolder = ''
|
||||
..hideTaskExtensionBanner = false;
|
||||
|
||||
static Serializer<PrefState> get serializer => _$prefStateSerializer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,6 +220,9 @@ class _$PrefStateSerializer implements StructuredSerializer<PrefState> {
|
|||
'hideTwoYearReviewApp',
|
||||
serializers.serialize(object.hideTwoYearReviewApp,
|
||||
specifiedType: const FullType(bool)),
|
||||
'hideTaskExtensionBanner',
|
||||
serializers.serialize(object.hideTaskExtensionBanner,
|
||||
specifiedType: const FullType(bool)),
|
||||
'editAfterSaving',
|
||||
serializers.serialize(object.editAfterSaving,
|
||||
specifiedType: const FullType(bool)),
|
||||
|
|
@ -402,6 +405,10 @@ class _$PrefStateSerializer implements StructuredSerializer<PrefState> {
|
|||
result.hideTwoYearReviewApp = serializers.deserialize(value,
|
||||
specifiedType: const FullType(bool))! as bool;
|
||||
break;
|
||||
case 'hideTaskExtensionBanner':
|
||||
result.hideTaskExtensionBanner = serializers.deserialize(value,
|
||||
specifiedType: const FullType(bool))! as bool;
|
||||
break;
|
||||
case 'editAfterSaving':
|
||||
result.editAfterSaving = serializers.deserialize(value,
|
||||
specifiedType: const FullType(bool))! as bool;
|
||||
|
|
@ -742,6 +749,8 @@ class _$PrefState extends PrefState {
|
|||
@override
|
||||
final bool hideTwoYearReviewApp;
|
||||
@override
|
||||
final bool hideTaskExtensionBanner;
|
||||
@override
|
||||
final bool editAfterSaving;
|
||||
@override
|
||||
final bool enableNativeBrowser;
|
||||
|
|
@ -792,6 +801,7 @@ class _$PrefState extends PrefState {
|
|||
required this.hideReviewApp,
|
||||
required this.hideOneYearReviewApp,
|
||||
required this.hideTwoYearReviewApp,
|
||||
required this.hideTaskExtensionBanner,
|
||||
required this.editAfterSaving,
|
||||
required this.enableNativeBrowser,
|
||||
required this.textScaleFactor,
|
||||
|
|
@ -865,6 +875,8 @@ class _$PrefState extends PrefState {
|
|||
hideOneYearReviewApp, r'PrefState', 'hideOneYearReviewApp');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
hideTwoYearReviewApp, r'PrefState', 'hideTwoYearReviewApp');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
hideTaskExtensionBanner, r'PrefState', 'hideTaskExtensionBanner');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
editAfterSaving, r'PrefState', 'editAfterSaving');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
|
|
@ -924,6 +936,7 @@ class _$PrefState extends PrefState {
|
|||
hideReviewApp == other.hideReviewApp &&
|
||||
hideOneYearReviewApp == other.hideOneYearReviewApp &&
|
||||
hideTwoYearReviewApp == other.hideTwoYearReviewApp &&
|
||||
hideTaskExtensionBanner == other.hideTaskExtensionBanner &&
|
||||
editAfterSaving == other.editAfterSaving &&
|
||||
enableNativeBrowser == other.enableNativeBrowser &&
|
||||
textScaleFactor == other.textScaleFactor &&
|
||||
|
|
@ -971,6 +984,7 @@ class _$PrefState extends PrefState {
|
|||
_$hash = $jc(_$hash, hideReviewApp.hashCode);
|
||||
_$hash = $jc(_$hash, hideOneYearReviewApp.hashCode);
|
||||
_$hash = $jc(_$hash, hideTwoYearReviewApp.hashCode);
|
||||
_$hash = $jc(_$hash, hideTaskExtensionBanner.hashCode);
|
||||
_$hash = $jc(_$hash, editAfterSaving.hashCode);
|
||||
_$hash = $jc(_$hash, enableNativeBrowser.hashCode);
|
||||
_$hash = $jc(_$hash, textScaleFactor.hashCode);
|
||||
|
|
@ -1018,6 +1032,7 @@ class _$PrefState extends PrefState {
|
|||
..add('hideReviewApp', hideReviewApp)
|
||||
..add('hideOneYearReviewApp', hideOneYearReviewApp)
|
||||
..add('hideTwoYearReviewApp', hideTwoYearReviewApp)
|
||||
..add('hideTaskExtensionBanner', hideTaskExtensionBanner)
|
||||
..add('editAfterSaving', editAfterSaving)
|
||||
..add('enableNativeBrowser', enableNativeBrowser)
|
||||
..add('textScaleFactor', textScaleFactor)
|
||||
|
|
@ -1199,6 +1214,11 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
|
|||
set hideTwoYearReviewApp(bool? hideTwoYearReviewApp) =>
|
||||
_$this._hideTwoYearReviewApp = hideTwoYearReviewApp;
|
||||
|
||||
bool? _hideTaskExtensionBanner;
|
||||
bool? get hideTaskExtensionBanner => _$this._hideTaskExtensionBanner;
|
||||
set hideTaskExtensionBanner(bool? hideTaskExtensionBanner) =>
|
||||
_$this._hideTaskExtensionBanner = hideTaskExtensionBanner;
|
||||
|
||||
bool? _editAfterSaving;
|
||||
bool? get editAfterSaving => _$this._editAfterSaving;
|
||||
set editAfterSaving(bool? editAfterSaving) =>
|
||||
|
|
@ -1272,6 +1292,7 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
|
|||
_hideReviewApp = $v.hideReviewApp;
|
||||
_hideOneYearReviewApp = $v.hideOneYearReviewApp;
|
||||
_hideTwoYearReviewApp = $v.hideTwoYearReviewApp;
|
||||
_hideTaskExtensionBanner = $v.hideTaskExtensionBanner;
|
||||
_editAfterSaving = $v.editAfterSaving;
|
||||
_enableNativeBrowser = $v.enableNativeBrowser;
|
||||
_textScaleFactor = $v.textScaleFactor;
|
||||
|
|
@ -1344,6 +1365,7 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
|
|||
hideReviewApp: BuiltValueNullFieldError.checkNotNull(hideReviewApp, r'PrefState', 'hideReviewApp'),
|
||||
hideOneYearReviewApp: BuiltValueNullFieldError.checkNotNull(hideOneYearReviewApp, r'PrefState', 'hideOneYearReviewApp'),
|
||||
hideTwoYearReviewApp: BuiltValueNullFieldError.checkNotNull(hideTwoYearReviewApp, r'PrefState', 'hideTwoYearReviewApp'),
|
||||
hideTaskExtensionBanner: BuiltValueNullFieldError.checkNotNull(hideTaskExtensionBanner, r'PrefState', 'hideTaskExtensionBanner'),
|
||||
editAfterSaving: BuiltValueNullFieldError.checkNotNull(editAfterSaving, r'PrefState', 'editAfterSaving'),
|
||||
enableNativeBrowser: BuiltValueNullFieldError.checkNotNull(enableNativeBrowser, r'PrefState', 'enableNativeBrowser'),
|
||||
textScaleFactor: BuiltValueNullFieldError.checkNotNull(textScaleFactor, r'PrefState', 'textScaleFactor'),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// Flutter imports:
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:invoiceninja_flutter/redux/task_status/task_status_selectors.dart';
|
||||
import 'package:invoiceninja_flutter/ui/app/icon_text.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
// Project imports:
|
||||
|
|
@ -23,6 +25,7 @@ import 'package:invoiceninja_flutter/ui/task/task_screen_vm.dart';
|
|||
import 'package:invoiceninja_flutter/utils/icons.dart';
|
||||
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||
import 'package:invoiceninja_flutter/utils/platforms.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class TaskScreen extends StatelessWidget {
|
||||
const TaskScreen({
|
||||
|
|
@ -40,18 +43,18 @@ class TaskScreen extends StatelessWidget {
|
|||
final state = store.state;
|
||||
final company = store.state.company;
|
||||
final userCompany = store.state.userCompany;
|
||||
final localization = AppLocalization.of(context);
|
||||
final localization = AppLocalization.of(context)!;
|
||||
final statuses = [
|
||||
TaskStatusEntity().rebuild((b) => b
|
||||
..id = kTaskStatusLogged
|
||||
..name = localization!.logged),
|
||||
..name = localization.logged),
|
||||
TaskStatusEntity().rebuild((b) => b
|
||||
..id = kTaskStatusRunning
|
||||
..name = localization!.running),
|
||||
..name = localization.running),
|
||||
if (!state.prefState.showKanban)
|
||||
TaskStatusEntity().rebuild((b) => b
|
||||
..id = kTaskStatusInvoiced
|
||||
..name = localization!.invoiced),
|
||||
..name = localization.invoiced),
|
||||
for (var statusId in memoizedSortedActiveTaskStatusIds(
|
||||
state.taskStatusState.list, state.taskStatusState.map))
|
||||
TaskStatusEntity().rebuild((b) => b
|
||||
|
|
@ -99,8 +102,57 @@ class TaskScreen extends StatelessWidget {
|
|||
},
|
||||
)
|
||||
],
|
||||
body:
|
||||
state.prefState.showKanban ? KanbanViewBuilder() : TaskListBuilder(),
|
||||
body: Column(children: [
|
||||
// TODO once Firefox is supported
|
||||
if (!state.prefState.hideTaskExtensionBanner &&
|
||||
isDesktop(context) &&
|
||||
(!kIsWeb || isChrome()))
|
||||
ColoredBox(
|
||||
color: Colors.orange,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: IconText(
|
||||
text: localization.taskExtensionBanner,
|
||||
icon: Icons.info_outline,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kTaskExtensionYouTubeUrl));
|
||||
},
|
||||
child: Text(
|
||||
localization.watchVideo,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kTaskExtensionUrl));
|
||||
},
|
||||
child: Text(
|
||||
localization.viewExtension,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: localization.dismiss,
|
||||
onPressed: () {
|
||||
store.dispatch(DismissTaskExtensionBanner());
|
||||
},
|
||||
icon: Icon(Icons.clear),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: state.prefState.showKanban
|
||||
? KanbanViewBuilder()
|
||||
: TaskListBuilder(),
|
||||
),
|
||||
]),
|
||||
bottomNavigationBar: AppBottomBar(
|
||||
entityType: EntityType.task,
|
||||
iconButtons: [
|
||||
|
|
@ -166,7 +218,7 @@ class TaskScreen extends StatelessWidget {
|
|||
Icons.add,
|
||||
color: Colors.white,
|
||||
),
|
||||
tooltip: localization!.newTask,
|
||||
tooltip: localization.newTask,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
|||
static final Map<String, Map<String, String>> _localizedValues = {
|
||||
'en': {
|
||||
// STARTER: lang key - do not remove comment
|
||||
'task_extension_banner': 'Add the Chrome extension to manage your tasks',
|
||||
'watch_video': 'Watch Video',
|
||||
'view_extension': 'View Extension',
|
||||
'reactivate_email': 'Reactivate Email',
|
||||
'email_reactivated': 'Successfully reactivated email',
|
||||
'template_help': 'Enable using the design as a template',
|
||||
|
|
@ -109980,6 +109983,18 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
|||
_localizedValues[localeCode]!['email_reactivated'] ??
|
||||
_localizedValues['en']!['email_reactivated']!;
|
||||
|
||||
String get taskExtensionBanner =>
|
||||
_localizedValues[localeCode]!['task_extension_banner'] ??
|
||||
_localizedValues['en']!['task_extension_banner']!;
|
||||
|
||||
String get watchVideo =>
|
||||
_localizedValues[localeCode]!['watch_video'] ??
|
||||
_localizedValues['en']!['watch_video']!;
|
||||
|
||||
String get viewExtension =>
|
||||
_localizedValues[localeCode]!['view_extension'] ??
|
||||
_localizedValues['en']!['view_extension']!;
|
||||
|
||||
// STARTER: lang field - do not remove comment
|
||||
|
||||
String lookup(String? key) {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,13 @@ String getPlatformName() {
|
|||
return 'Unknown';
|
||||
}
|
||||
|
||||
bool isChrome() {
|
||||
String userAgent = WebUtils.getHtmlValue('user-agent') ?? '';
|
||||
userAgent = userAgent.toLowerCase();
|
||||
|
||||
return userAgent.contains('chrome');
|
||||
}
|
||||
|
||||
String getNativePlatform() {
|
||||
String userAgent = WebUtils.getHtmlValue('user-agent') ?? '';
|
||||
userAgent = userAgent.toLowerCase();
|
||||
|
|
|
|||
Loading…
Reference in New Issue