Large icons (#237)

This commit is contained in:
Vishnu Mohandas 2023-09-08 18:32:14 +05:30 committed by GitHub
commit fddf9169d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 227 additions and 124 deletions

View file

@ -0,0 +1,3 @@
import 'package:ente_auth/events/event.dart';
class IconsChangedEvent extends Event {}

View file

@ -1,3 +1,5 @@
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/icons_changed_event.dart';
import 'package:shared_preferences/shared_preferences.dart';
class PreferenceService {
@ -8,6 +10,7 @@ class PreferenceService {
late final SharedPreferences _prefs;
static const kHasShownCoachMarkKey = "has_shown_coach_mark";
static const kShouldShowLargeIconsKey = "should_show_large_icons";
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
@ -24,4 +27,17 @@ class PreferenceService {
Future<void> setHasShownCoachMark(bool value) {
return _prefs.setBool(kHasShownCoachMarkKey, value);
}
bool shouldShowLargeIcons() {
if (_prefs.containsKey(kShouldShowLargeIconsKey)) {
return _prefs.getBool(kShouldShowLargeIconsKey)!;
} else {
return false;
}
}
Future<void> setShowLargeIcons(bool value) async {
await _prefs.setBool(kShouldShowLargeIconsKey, value);
Bus.instance.fire(IconsChangedEvent());
}
}

View file

@ -7,6 +7,7 @@ import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart';
import 'package:ente_auth/onboarding/view/view_qr_page.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/code_timer_progress.dart';
import 'package:ente_auth/ui/utils/icon_utils.dart';
@ -15,7 +16,9 @@ import 'package:ente_auth/utils/toast_util.dart';
import 'package:ente_auth/utils/totp_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:local_hero/local_hero.dart';
import 'package:logging/logging.dart';
import 'package:uuid/uuid.dart';
class CodeWidget extends StatefulWidget {
final Code code;
@ -33,6 +36,8 @@ class _CodeWidgetState extends State<CodeWidget> {
final Logger logger = Logger("_CodeWidgetState");
bool _isInitialized = false;
late bool hasConfiguredAccount;
late bool _shouldShowLargeIcon;
final String _key = const Uuid().v4();
@override
void initState() {
@ -61,6 +66,7 @@ class _CodeWidgetState extends State<CodeWidget> {
@override
Widget build(BuildContext context) {
_shouldShowLargeIcon = PreferenceService.instance.shouldShowLargeIcons();
if (!_isInitialized) {
_currentCode.value = _getCurrentOTP();
if (widget.code.type == Type.totp) {
@ -136,134 +142,186 @@ class _CodeWidgetState extends State<CodeWidget> {
onLongPress: () {
_copyToClipboard();
},
child: SizedBox(
child: _getCardContents(l10n),
),
),
),
),
),
);
}
Widget _getCardContents(AppLocalizations l10n) {
return LocalHeroScope(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
child: SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.code.type == Type.totp)
CodeTimerProgress(
period: widget.code.period,
),
const SizedBox(
height: 16,
),
Row(
children: [
_shouldShowLargeIcon ? _getIcon() : const SizedBox.shrink(),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.code.type == Type.totp)
CodeTimerProgress(
period: widget.code.period,
),
const SizedBox(
height: 16,
),
Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
safeDecode(widget.code.issuer).trim(),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 2),
Text(
safeDecode(widget.code.account).trim(),
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
fontSize: 12,
color: Colors.grey,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
(widget.code.hasSynced != null &&
widget.code.hasSynced!) || !hasConfiguredAccount
? const SizedBox.shrink()
: const Icon(
Icons.sync_disabled,
size: 20,
color: Colors.amber,
),
const SizedBox(width: 12),
IconUtils.instance.getIcon(
safeDecode(widget.code.issuer).trim(),
),
],
),
],
),
),
_getTopRow(),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: ValueListenableBuilder<String>(
valueListenable: _currentCode,
builder: (context, value, child) {
return Text(
value,
style: const TextStyle(fontSize: 24),
);
},
),
),
widget.code.type == Type.totp
? Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
l10n.nextTotpTitle,
style:
Theme.of(context).textTheme.bodySmall,
),
ValueListenableBuilder<String>(
valueListenable: _nextCode,
builder: (context, value, child) {
return Text(
value,
style: const TextStyle(
fontSize: 18,
color: Colors.grey,
),
);
},
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
l10n.nextTotpTitle,
style:
Theme.of(context).textTheme.bodySmall,
),
InkWell(
onTap: _onNextHotpTapped,
child: const Icon(
Icons.forward_outlined,
size: 32,
color: Colors.grey,
),
),
],
),
],
),
),
const SizedBox(
height: 20,
),
_getBottomRow(l10n),
],
),
),
],
),
const SizedBox(
height: 20,
),
],
),
),
);
}
Widget _getBottomRow(AppLocalizations l10n) {
return LocalHero(
tag: _key + "_bottom_row",
child: Container(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: ValueListenableBuilder<String>(
valueListenable: _currentCode,
builder: (context, value, child) {
return Material(
type: MaterialType.transparency,
child: Text(
value,
style: const TextStyle(fontSize: 24),
),
);
},
),
),
widget.code.type == Type.totp
? Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
l10n.nextTotpTitle,
style: Theme.of(context).textTheme.bodySmall,
),
ValueListenableBuilder<String>(
valueListenable: _nextCode,
builder: (context, value, child) {
return Material(
type: MaterialType.transparency,
child: Text(
value,
style: const TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
);
},
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
l10n.nextTotpTitle,
style: Theme.of(context).textTheme.bodySmall,
),
InkWell(
onTap: _onNextHotpTapped,
child: const Icon(
Icons.forward_outlined,
size: 32,
color: Colors.grey,
),
),
],
),
],
),
),
);
}
Widget _getTopRow() {
return Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocalHero(
tag: _key + "_top_row",
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
safeDecode(widget.code.issuer).trim(),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 2),
Text(
safeDecode(widget.code.account).trim(),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
(widget.code.hasSynced != null && widget.code.hasSynced!) ||
!hasConfiguredAccount
? const SizedBox.shrink()
: const Icon(
Icons.sync_disabled,
size: 20,
color: Colors.amber,
),
const SizedBox(width: 12),
_shouldShowLargeIcon ? const SizedBox.shrink() : _getIcon(),
],
),
],
),
);
}
Widget _getIcon() {
return LocalHero(
tag: _key,
child: Padding(
padding: _shouldShowLargeIcon
? const EdgeInsets.only(left: 16)
: const EdgeInsets.all(0),
child: GestureDetector(
onTap: () {
PreferenceService.instance.setShowLargeIcons(!_shouldShowLargeIcon);
},
child: IconUtils.instance.getIcon(
safeDecode(widget.code.issuer).trim(),
width: _shouldShowLargeIcon ? 42 : 24,
),
),
),
@ -300,6 +358,7 @@ class _CodeWidgetState extends State<CodeWidget> {
}
Future<void> _onShowQrPressed(_) async {
// ignore: unused_local_variable
final Code? code = await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {

View file

@ -5,6 +5,7 @@ import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/events/codes_updated_event.dart';
import 'package:ente_auth/events/icons_changed_event.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
import "package:ente_auth/l10n/l10n.dart";
import 'package:ente_auth/models/code.dart';
@ -51,6 +52,7 @@ class _HomePageState extends State<HomePage> {
List<Code> _filteredCodes = [];
StreamSubscription<CodesUpdatedEvent>? _streamSubscription;
StreamSubscription<TriggerLogoutEvent>? _triggerLogoutEvent;
StreamSubscription<IconsChangedEvent>? _iconsChangedEvent;
@override
void initState() {
@ -69,6 +71,9 @@ class _HomePageState extends State<HomePage> {
const Duration(seconds: 1),
() async => await CodeStore.instance.importOfflineCodes(),
);
_iconsChangedEvent = Bus.instance.on<IconsChangedEvent>().listen((event) {
setState(() {});
});
}
void _loadCodes() {
@ -100,6 +105,7 @@ class _HomePageState extends State<HomePage> {
void dispose() {
_streamSubscription?.cancel();
_triggerLogoutEvent?.cancel();
_iconsChangedEvent?.cancel();
_textController.removeListener(_applyFilteringAndRefresh);
super.dispose();
}
@ -228,7 +234,7 @@ class _HomePageState extends State<HomePage> {
itemBuilder: ((context, index) {
try {
return CodeWidget(_filteredCodes[index]);
} catch(e) {
} catch (e) {
return const Text("Failed");
}
}),

View file

@ -18,29 +18,39 @@ class IconUtils {
await _loadJson();
}
Widget getIcon(String provider) {
Widget getIcon(
String provider, {
double width = 24,
}) {
final title = _getProviderTitle(provider);
if (_customIcons.containsKey(title)) {
return _getSVGIcon(
"assets/custom-icons/icons/$title.svg",
title,
_customIcons[title]!,
width,
);
} else if (_simpleIcons.containsKey(title)) {
return _getSVGIcon(
"assets/simple-icons/icons/$title.svg",
title,
_simpleIcons[title]!,
width,
);
} else {
return const SizedBox.shrink();
}
}
Widget _getSVGIcon(String path, String title, String color) {
Widget _getSVGIcon(
String path,
String title,
String color,
double width,
) {
return SvgPicture.asset(
path,
width: 24,
width: width,
semanticsLabel: title,
colorFilter: ColorFilter.mode(
Color(int.parse("0xFF" + color)),

View file

@ -783,6 +783,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
local_hero:
dependency: "direct main"
description:
name: local_hero
sha256: "2dd2904c46d786dbc6f7179ba863e04f2be1fd603c530501a336a07744b60c7b"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
logging:
dependency: "direct main"
description:

View file

@ -51,6 +51,7 @@ dependencies:
intl: ^0.18.0
json_annotation: ^4.5.0
local_auth: ^2.1.3
local_hero: ^0.2.0
logging: ^1.0.1
modal_bottom_sheet: ^3.0.0-pre
move_to_background: ^1.0.2