Merge pull request #303 from ente-io/branding

Branding
This commit is contained in:
Vishnu Mohandas 2022-06-12 23:47:09 +05:30 committed by GitHub
commit 956a3cbfb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 472 additions and 288 deletions

View file

@ -151,7 +151,7 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
children: [
const SizedBox(height: 12),
SectionTitle("On device"),
const SizedBox(height: 24),
const SizedBox(height: 12),
items.folders.isEmpty
? Padding(
padding: const EdgeInsets.all(22),
@ -191,11 +191,11 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SectionTitle("On ente"),
EnteSectionTitle(),
_sortMenu(),
],
),
const SizedBox(height: 24),
const SizedBox(height: 12),
Configuration.instance.hasConfiguredAccount()
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@ -746,3 +746,48 @@ class SectionTitle extends StatelessWidget {
);
}
}
class EnteSectionTitle extends StatelessWidget {
final double opacity;
const EnteSectionTitle({
this.opacity = 0.8,
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.fromLTRB(16, 12, 0, 0),
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: "On ",
style: Theme.of(context)
.textTheme
.headline6
.copyWith(fontSize: 22),
),
TextSpan(
text: "ente",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
fontSize: 22,
color: Theme.of(context).colorScheme.defaultTextColor,
),
),
],
),
),
),
],
),
);
}
}

View file

@ -0,0 +1,163 @@
import 'package:flutter/material.dart';
import 'package:photos/core/errors.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/ui/payment/subscription.dart';
import 'package:photos/utils/email_util.dart';
class HeaderErrorWidget extends StatelessWidget {
final Error _error;
const HeaderErrorWidget({Key key, @required Error error})
: _error = error,
super(key: key);
@override
Widget build(BuildContext context) {
if (_error is NoActiveSubscriptionError) {
return Container(
margin: EdgeInsets.only(top: 8),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(
Icons.error_outline,
color: Colors.orange,
),
Padding(padding: EdgeInsets.all(4)),
Text("Your subscription has expired"),
],
),
Padding(padding: EdgeInsets.all(8)),
Container(
width: 400,
height: 52,
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
child: OutlinedButton(
child: Text("Subscribe"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return getSubscriptionPage();
},
),
);
},
),
),
Padding(padding: EdgeInsets.all(12)),
Divider(
thickness: 2,
height: 0,
),
Padding(padding: EdgeInsets.all(12)),
],
),
);
} else if (_error is StorageLimitExceededError) {
return Container(
margin: EdgeInsets.only(top: 8),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(
Icons.error_outline,
color: Colors.orange,
),
Padding(padding: EdgeInsets.all(4)),
Text("Storage limit exceeded"),
],
),
Padding(padding: EdgeInsets.all(8)),
Container(
width: 400,
height: 52,
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
child: OutlinedButton(
child: Text("Upgrade"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return getSubscriptionPage();
},
),
);
},
),
),
Padding(padding: EdgeInsets.all(12)),
Divider(
thickness: 2,
height: 0,
),
Padding(padding: EdgeInsets.all(12)),
],
),
);
} else {
return Center(
child: Column(
children: [
Icon(
Icons.error_outline,
color: Colors.red[400],
),
Padding(padding: EdgeInsets.all(4)),
Text(
"We could not backup your data.\nWe will retry later.",
style: TextStyle(height: 1.4),
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(8)),
InkWell(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor:
Theme.of(context).colorScheme.inverseTextColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.fromLTRB(50, 16, 50, 16),
side: BorderSide(
width: 2,
color: Colors.orange[600],
),
),
child: Text(
"Raise ticket",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.orange[600],
),
textAlign: TextAlign.center,
),
onPressed: () {
sendLogs(
context,
"Raise ticket",
"support@ente.io",
subject: "Backup failed",
);
},
),
),
Padding(padding: EdgeInsets.all(16)),
Divider(
thickness: 2,
height: 0,
),
Padding(padding: EdgeInsets.all(12)),
],
),
);
}
}
}

View file

@ -48,7 +48,7 @@ import 'package:photos/ui/memories_widget.dart';
import 'package:photos/ui/nav_bar.dart';
import 'package:photos/ui/settings_page.dart';
import 'package:photos/ui/shared_collections_gallery.dart';
import 'package:photos/ui/sync_indicator.dart';
import 'package:photos/ui/status_bar_widget.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/navigation_util.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
@ -719,7 +719,7 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
class HeaderWidget extends StatelessWidget {
static const _memoriesWidget = MemoriesWidget();
static const _syncIndicator = SyncIndicator();
static const _statusBarWidget = StatusBarWidget();
const HeaderWidget({
Key key,
@ -729,7 +729,7 @@ class HeaderWidget extends StatelessWidget {
Widget build(BuildContext context) {
Logger("Header").info("Building header widget");
const list = [
_syncIndicator,
_statusBarWidget,
_memoriesWidget,
];
return Column(

View file

@ -125,7 +125,7 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery>
children: [
const SizedBox(height: 12),
SectionTitle("Incoming"),
const SizedBox(height: 24),
const SizedBox(height: 12),
collections.incoming.isNotEmpty
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@ -150,7 +150,6 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery>
: _getIncomingCollectionEmptyState(),
const SizedBox(height: 32),
SectionTitle("Outgoing"),
const SizedBox(height: 12),
collections.outgoing.isNotEmpty
? ListView.builder(
shrinkWrap: true,

View file

@ -0,0 +1,256 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/sync_status_update_event.dart';
import 'package:photos/services/sync_service.dart';
import 'package:photos/ui/header_error_widget.dart';
const double kContainerHeight = 48;
class StatusBarWidget extends StatefulWidget {
const StatusBarWidget({Key key}) : super(key: key);
@override
State<StatusBarWidget> createState() => _StatusBarWidgetState();
}
class _StatusBarWidgetState extends State<StatusBarWidget> {
StreamSubscription<SyncStatusUpdate> _subscription;
bool _showStatus = false;
@override
void initState() {
_subscription = Bus.instance.on<SyncStatusUpdate>().listen((event) {
if (event.status == SyncStatus.completed_first_gallery_import ||
event.status == SyncStatus.completed_backup) {
Future.delayed(Duration(milliseconds: 2000), () {
if (mounted) {
setState(() {
_showStatus = false;
});
}
});
} else {
setState(() {
_showStatus = true;
});
}
});
super.initState();
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
AnimatedOpacity(
opacity: _showStatus ? 0 : 1,
duration: Duration(milliseconds: 1000),
child: StatusBarBrandingWidget(),
),
AnimatedOpacity(
opacity: _showStatus ? 1 : 0,
duration: Duration(milliseconds: 1000),
child: SyncStatusWidget(),
),
],
);
}
}
class SyncStatusWidget extends StatefulWidget {
const SyncStatusWidget({Key key}) : super(key: key);
@override
_SyncStatusWidgetState createState() => _SyncStatusWidgetState();
}
class _SyncStatusWidgetState extends State<SyncStatusWidget> {
static const Duration kSleepDuration = Duration(milliseconds: 3000);
SyncStatusUpdate _event;
StreamSubscription<SyncStatusUpdate> _subscription;
@override
void initState() {
_subscription = Bus.instance.on<SyncStatusUpdate>().listen((event) {
setState(() {
_event = event;
});
});
_event = SyncService.instance.getLastSyncStatusEvent();
super.initState();
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
bool isNotOutdatedEvent = _event != null &&
(_event.status == SyncStatus.completed_backup ||
_event.status == SyncStatus.completed_first_gallery_import) &&
(DateTime.now().microsecondsSinceEpoch - _event.timestamp >
kSleepDuration.inMicroseconds);
if (_event == null || isNotOutdatedEvent) {
return Container();
}
if (_event.status == SyncStatus.error) {
return HeaderErrorWidget(error: _event.error);
}
if (_event.status == SyncStatus.completed_backup) {
return SyncStatusCompletedWidget();
}
return RefreshIndicatorWidget(_event);
}
}
class RefreshIndicatorWidget extends StatelessWidget {
static const _inProgressIcon = CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(45, 194, 98, 1.0)),
);
final SyncStatusUpdate event;
const RefreshIndicatorWidget(this.event, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: kContainerHeight,
width: double.infinity,
alignment: Alignment.center,
child: SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(2),
width: 22,
height: 22,
child: _inProgressIcon,
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 4, 0, 0),
child: Text(_getRefreshingText()),
),
],
),
Padding(padding: EdgeInsets.all(4)),
Divider(),
],
),
),
);
}
String _getRefreshingText() {
if (event.status == SyncStatus.started_first_gallery_import ||
event.status == SyncStatus.completed_first_gallery_import) {
return "Loading gallery...";
}
if (event.status == SyncStatus.applying_remote_diff) {
return "Syncing...";
}
if (event.status == SyncStatus.preparing_for_upload) {
return "Encrypting backup...";
}
if (event.status == SyncStatus.in_progress) {
return event.completed.toString() +
"/" +
event.total.toString() +
" Memories preserved";
}
if (event.status == SyncStatus.paused) {
return event.reason;
}
if (event.status == SyncStatus.error) {
return event.reason ?? "Upload failed";
}
if (event.status == SyncStatus.completed_backup) {
if (event.wasStopped) {
return "Sync stopped";
}
}
return "All memories preserved";
}
}
class StatusBarBrandingWidget extends StatelessWidget {
const StatusBarBrandingWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: kContainerHeight,
padding: EdgeInsets.only(left: 12),
child: Align(
alignment: Alignment.topLeft,
child: Text(
"ente",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
fontSize: 28,
height: 1,
),
),
),
);
}
}
class SyncStatusCompletedWidget extends StatelessWidget {
const SyncStatusCompletedWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: kContainerHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(2),
width: 22,
height: 22,
child: Icon(
Icons.cloud_done_outlined,
color: Theme.of(context).buttonColor,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 4, 0, 0),
child: Text("All memories preserved"),
),
],
),
Padding(padding: EdgeInsets.all(4)),
Divider(),
],
),
);
}
}

View file

@ -1,279 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:photos/core/errors.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/sync_status_update_event.dart';
import 'package:photos/services/sync_service.dart';
import 'package:photos/ui/payment/subscription.dart';
import 'package:photos/utils/email_util.dart';
class SyncIndicator extends StatefulWidget {
const SyncIndicator({Key key}) : super(key: key);
@override
_SyncIndicatorState createState() => _SyncIndicatorState();
}
class _SyncIndicatorState extends State<SyncIndicator> {
static const kSleepDuration = Duration(milliseconds: 3000);
SyncStatusUpdate _event;
double _containerHeight = 48;
StreamSubscription<SyncStatusUpdate> _subscription;
static const _inProgressIcon = CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(45, 194, 98, 1.0)),
);
@override
void initState() {
_subscription = Bus.instance.on<SyncStatusUpdate>().listen((event) {
setState(() {
_event = event;
});
});
_event = SyncService.instance.getLastSyncStatusEvent();
super.initState();
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
bool isNotOutdatedEvent = _event != null &&
(_event.status == SyncStatus.completed_backup ||
_event.status == SyncStatus.completed_first_gallery_import) &&
(DateTime.now().microsecondsSinceEpoch - _event.timestamp >
kSleepDuration.inMicroseconds);
if (_event == null || isNotOutdatedEvent) {
return Container();
}
if (_event.status == SyncStatus.error) {
return _getErrorWidget();
}
if (_event.status == SyncStatus.completed_first_gallery_import ||
_event.status == SyncStatus.completed_backup) {
Future.delayed(kSleepDuration, () {
if (mounted) {
setState(() {
_containerHeight = 0;
});
}
});
} else {
_containerHeight = 48;
}
final icon = _event.status == SyncStatus.completed_backup
? Icon(
Icons.cloud_done_outlined,
color: Theme.of(context).buttonColor,
)
: _inProgressIcon;
return AnimatedContainer(
duration: Duration(milliseconds: 300),
height: _containerHeight,
width: double.infinity,
padding: EdgeInsets.all(8),
alignment: Alignment.center,
child: SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(2),
width: 22,
height: 22,
child: icon,
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 4, 0, 0),
child: Text(_getRefreshingText()),
),
],
),
Padding(padding: EdgeInsets.all(4)),
Divider(),
],
),
),
);
}
Widget _getErrorWidget() {
if (_event.error is NoActiveSubscriptionError) {
return Container(
margin: EdgeInsets.only(top: 8),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
color: Theme.of(context).buttonColor,
),
Padding(padding: EdgeInsets.all(4)),
Text("Your subscription has expired"),
],
),
Padding(padding: EdgeInsets.all(6)),
Container(
width: double.infinity,
height: 64,
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
child: OutlinedButton(
child: Text("Subscribe"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return getSubscriptionPage();
},
),
);
},
),
),
Padding(padding: EdgeInsets.all(8)),
],
),
);
} else if (_event.error is StorageLimitExceededError) {
return Container(
margin: EdgeInsets.only(top: 8),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
color: Theme.of(context).buttonColor,
),
Padding(padding: EdgeInsets.all(4)),
Text("Storage limit exceeded"),
],
),
Padding(padding: EdgeInsets.all(6)),
Container(
width: double.infinity,
height: 64,
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
child: OutlinedButton(
child: Text("Upgrade"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return getSubscriptionPage();
},
),
);
},
),
),
Padding(padding: EdgeInsets.all(8)),
],
),
);
} else {
return Center(
child: Column(
children: [
Icon(
Icons.error_outline,
color: Colors.red[400],
),
Padding(padding: EdgeInsets.all(4)),
Text(
"We could not backup your data\nwe will retry later",
style: TextStyle(height: 1.4),
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(8)),
InkWell(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.fromLTRB(50, 16, 50, 16),
side: BorderSide(
width: 1,
color: Colors.orange[300],
),
),
child: Text(
"Raise ticket",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.orange[300],
),
textAlign: TextAlign.center,
),
onPressed: () {
sendLogs(
context,
"Raise ticket",
"support@ente.io",
subject: "Backup failed",
);
},
),
),
Padding(padding: EdgeInsets.all(16)),
Divider(
thickness: 2,
height: 0,
),
Padding(padding: EdgeInsets.all(12)),
],
),
);
}
}
String _getRefreshingText() {
if (_event.status == SyncStatus.started_first_gallery_import ||
_event.status == SyncStatus.completed_first_gallery_import) {
return "Loading gallery...";
}
if (_event.status == SyncStatus.applying_remote_diff) {
return "Syncing...";
}
if (_event.status == SyncStatus.preparing_for_upload) {
return "Encrypting backup...";
}
if (_event.status == SyncStatus.in_progress) {
return _event.completed.toString() +
"/" +
_event.total.toString() +
" Memories preserved";
}
if (_event.status == SyncStatus.paused) {
return _event.reason;
}
if (_event.status == SyncStatus.completed_backup) {
if (_event.wasStopped) {
return "Sync stopped";
} else {
return "All memories preserved";
}
}
// _event.status == SyncStatus.error
return _event.reason ?? "Upload failed";
}
}

View file

@ -11,7 +11,7 @@ description: ente photos application
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.5.37+317
version: 0.5.38+318
environment:
sdk: ">=2.10.0 <3.0.0"