mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
Cw 586 display user twitter image in birdpay (#1315)
* Update address_validator.dart * add twitter profile image * mastodon profile image * fix data types
This commit is contained in:
parent
2549b0fa0a
commit
10fd32fb2e
9 changed files with 200 additions and 86 deletions
|
@ -270,11 +270,11 @@ class AddressValidator extends TextValidator {
|
||||||
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)8[0-9a-zA-Z]{94}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
|
'|([^0-9a-zA-Z]|^)[0-9a-zA-Z]{106}([^0-9a-zA-Z]|\$)';
|
||||||
case CryptoCurrency.btc:
|
case CryptoCurrency.btc:
|
||||||
return '([^0-9a-zA-Z]|^)${P2pkhAddress.regex.pattern}|\$)'
|
return '([^0-9a-zA-Z]|^)([1mn][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2pkhAddress type
|
||||||
'([^0-9a-zA-Z]|^)${P2shAddress.regex.pattern}|\$)'
|
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
|
||||||
'([^0-9a-zA-Z]|^)${P2wpkhAddress.regex.pattern}|\$)'
|
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
|
||||||
'([^0-9a-zA-Z]|^)${P2wshAddress.regex.pattern}|\$)'
|
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
|
||||||
'([^0-9a-zA-Z]|^)${P2trAddress.regex.pattern}|\$)';
|
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)'; //P2trAddress type
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||||
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
|
||||||
|
|
|
@ -69,16 +69,20 @@ class AddressResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Future<ParsedAddress> resolve(BuildContext context, String text, String ticker) async {
|
Future<ParsedAddress> resolve(BuildContext context, String text, String ticker) async {
|
||||||
try {
|
try {
|
||||||
if (text.startsWith('@') && !text.substring(1).contains('@')) {
|
if (text.startsWith('@') && !text.substring(1).contains('@')) {
|
||||||
if(settingsStore.lookupsTwitter) {
|
if (settingsStore.lookupsTwitter) {
|
||||||
final formattedName = text.substring(1);
|
final formattedName = text.substring(1);
|
||||||
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
|
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
|
||||||
final addressFromBio = extractAddressByType(
|
final addressFromBio = extractAddressByType(
|
||||||
raw: twitterUser.description, type: CryptoCurrency.fromString(ticker));
|
raw: twitterUser.description, type: CryptoCurrency.fromString(ticker));
|
||||||
if (addressFromBio != null) {
|
if (addressFromBio != null) {
|
||||||
return ParsedAddress.fetchTwitterAddress(address: addressFromBio, name: text);
|
return ParsedAddress.fetchTwitterAddress(
|
||||||
|
address: addressFromBio,
|
||||||
|
name: text,
|
||||||
|
profileImageUrl: twitterUser.profileImageUrl,
|
||||||
|
profileName: twitterUser.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
final pinnedTweet = twitterUser.pinnedTweet?.text;
|
final pinnedTweet = twitterUser.pinnedTweet?.text;
|
||||||
|
@ -86,7 +90,11 @@ class AddressResolver {
|
||||||
final addressFromPinnedTweet =
|
final addressFromPinnedTweet =
|
||||||
extractAddressByType(raw: pinnedTweet, type: CryptoCurrency.fromString(ticker));
|
extractAddressByType(raw: pinnedTweet, type: CryptoCurrency.fromString(ticker));
|
||||||
if (addressFromPinnedTweet != null) {
|
if (addressFromPinnedTweet != null) {
|
||||||
return ParsedAddress.fetchTwitterAddress(address: addressFromPinnedTweet, name: text);
|
return ParsedAddress.fetchTwitterAddress(
|
||||||
|
address: addressFromPinnedTweet,
|
||||||
|
name: text,
|
||||||
|
profileImageUrl: twitterUser.profileImageUrl,
|
||||||
|
profileName: twitterUser.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +115,11 @@ class AddressResolver {
|
||||||
extractAddressByType(raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker));
|
extractAddressByType(raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker));
|
||||||
|
|
||||||
if (addressFromBio != null) {
|
if (addressFromBio != null) {
|
||||||
return ParsedAddress.fetchMastodonAddress(address: addressFromBio, name: text);
|
return ParsedAddress.fetchMastodonAddress(
|
||||||
|
address: addressFromBio,
|
||||||
|
name: text,
|
||||||
|
profileImageUrl: mastodonUser.profileImageUrl,
|
||||||
|
profileName: mastodonUser.username);
|
||||||
} else {
|
} else {
|
||||||
final pinnedPosts =
|
final pinnedPosts =
|
||||||
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
|
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
|
||||||
|
@ -119,7 +131,10 @@ class AddressResolver {
|
||||||
|
|
||||||
if (addressFromPinnedPost != null) {
|
if (addressFromPinnedPost != null) {
|
||||||
return ParsedAddress.fetchMastodonAddress(
|
return ParsedAddress.fetchMastodonAddress(
|
||||||
address: addressFromPinnedPost, name: text);
|
address: addressFromPinnedPost,
|
||||||
|
name: text,
|
||||||
|
profileImageUrl: mastodonUser.profileImageUrl,
|
||||||
|
profileName: mastodonUser.username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:cake_wallet/entities/openalias_record.dart';
|
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||||
import 'package:cake_wallet/entities/yat_record.dart';
|
import 'package:cake_wallet/entities/yat_record.dart';
|
||||||
|
|
||||||
|
|
||||||
enum ParseFrom {
|
enum ParseFrom {
|
||||||
unstoppableDomains,
|
unstoppableDomains,
|
||||||
openAlias,
|
openAlias,
|
||||||
|
@ -20,36 +19,37 @@ class ParsedAddress {
|
||||||
required this.addresses,
|
required this.addresses,
|
||||||
this.name = '',
|
this.name = '',
|
||||||
this.description = '',
|
this.description = '',
|
||||||
|
this.profileImageUrl = '',
|
||||||
|
this.profileName = '',
|
||||||
this.parseFrom = ParseFrom.notParsed,
|
this.parseFrom = ParseFrom.notParsed,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ParsedAddress.fetchEmojiAddress({
|
factory ParsedAddress.fetchEmojiAddress({
|
||||||
List<YatRecord>? addresses,
|
List<YatRecord>? addresses,
|
||||||
required String name,
|
required String name,
|
||||||
}){
|
}) {
|
||||||
if (addresses?.isEmpty ?? true) {
|
if (addresses?.isEmpty ?? true) {
|
||||||
return ParsedAddress(
|
return ParsedAddress(addresses: [name], parseFrom: ParseFrom.yatRecord);
|
||||||
addresses: [name], parseFrom: ParseFrom.yatRecord);
|
}
|
||||||
}
|
return ParsedAddress(
|
||||||
return ParsedAddress(
|
addresses: addresses!.map((e) => e.address).toList(),
|
||||||
addresses: addresses!.map((e) => e.address).toList(),
|
name: name,
|
||||||
name: name,
|
parseFrom: ParseFrom.yatRecord,
|
||||||
parseFrom: ParseFrom.yatRecord,
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ParsedAddress.fetchUnstoppableDomainAddress({
|
factory ParsedAddress.fetchUnstoppableDomainAddress({
|
||||||
String? address,
|
String? address,
|
||||||
required String name,
|
required String name,
|
||||||
}){
|
}) {
|
||||||
if (address?.isEmpty ?? true) {
|
if (address?.isEmpty ?? true) {
|
||||||
return ParsedAddress(addresses: [name]);
|
return ParsedAddress(addresses: [name]);
|
||||||
}
|
}
|
||||||
return ParsedAddress(
|
return ParsedAddress(
|
||||||
addresses: [address!],
|
addresses: [address!],
|
||||||
name: name,
|
name: name,
|
||||||
parseFrom: ParseFrom.unstoppableDomains,
|
parseFrom: ParseFrom.unstoppableDomains,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ParsedAddress.fetchOpenAliasAddress(
|
factory ParsedAddress.fetchOpenAliasAddress(
|
||||||
|
@ -65,7 +65,7 @@ class ParsedAddress {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ParsedAddress.fetchFioAddress({required String address, required String name}){
|
factory ParsedAddress.fetchFioAddress({required String address, required String name}) {
|
||||||
return ParsedAddress(
|
return ParsedAddress(
|
||||||
addresses: [address],
|
addresses: [address],
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -73,23 +73,37 @@ class ParsedAddress {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ParsedAddress.fetchTwitterAddress({required String address, required String name}){
|
factory ParsedAddress.fetchTwitterAddress(
|
||||||
|
{required String address,
|
||||||
|
required String name,
|
||||||
|
required String profileImageUrl,
|
||||||
|
required String profileName,
|
||||||
|
String? description}) {
|
||||||
return ParsedAddress(
|
return ParsedAddress(
|
||||||
addresses: [address],
|
addresses: [address],
|
||||||
name: name,
|
name: name,
|
||||||
|
description: description ?? '',
|
||||||
|
profileImageUrl: profileImageUrl,
|
||||||
|
profileName: profileName,
|
||||||
parseFrom: ParseFrom.twitter,
|
parseFrom: ParseFrom.twitter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ParsedAddress.fetchMastodonAddress({required String address, required String name}){
|
factory ParsedAddress.fetchMastodonAddress(
|
||||||
|
{required String address,
|
||||||
|
required String name,
|
||||||
|
required String profileImageUrl,
|
||||||
|
required String profileName}) {
|
||||||
return ParsedAddress(
|
return ParsedAddress(
|
||||||
addresses: [address],
|
addresses: [address],
|
||||||
name: name,
|
name: name,
|
||||||
parseFrom: ParseFrom.mastodon
|
parseFrom: ParseFrom.mastodon,
|
||||||
|
profileImageUrl: profileImageUrl,
|
||||||
|
profileName: profileName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ParsedAddress.fetchContactAddress({required String address, required String name}){
|
factory ParsedAddress.fetchContactAddress({required String address, required String name}) {
|
||||||
return ParsedAddress(
|
return ParsedAddress(
|
||||||
addresses: [address],
|
addresses: [address],
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -116,6 +130,7 @@ class ParsedAddress {
|
||||||
final List<String> addresses;
|
final List<String> addresses;
|
||||||
final String name;
|
final String name;
|
||||||
final String description;
|
final String description;
|
||||||
|
final String profileImageUrl;
|
||||||
|
final String profileName;
|
||||||
final ParseFrom parseFrom;
|
final ParseFrom parseFrom;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
class MastodonUser {
|
class MastodonUser {
|
||||||
String id;
|
String id;
|
||||||
String username;
|
String username;
|
||||||
|
String profileImageUrl;
|
||||||
String acct;
|
String acct;
|
||||||
String note;
|
String note;
|
||||||
|
|
||||||
MastodonUser({
|
MastodonUser({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.username,
|
required this.username,
|
||||||
|
required this.profileImageUrl,
|
||||||
required this.acct,
|
required this.acct,
|
||||||
required this.note,
|
required this.note,
|
||||||
});
|
});
|
||||||
|
@ -14,9 +16,10 @@ class MastodonUser {
|
||||||
factory MastodonUser.fromJson(Map<String, dynamic> json) {
|
factory MastodonUser.fromJson(Map<String, dynamic> json) {
|
||||||
return MastodonUser(
|
return MastodonUser(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
username: json['username'] as String,
|
username: json['username'] as String? ?? '',
|
||||||
acct: json['acct'] as String,
|
acct: json['acct'] as String,
|
||||||
note: json['note'] as String,
|
note: json['note'] as String,
|
||||||
|
profileImageUrl: json['avatar'] as String? ?? ''
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ Future<String> extractAddressFromParsed(
|
||||||
var title = '';
|
var title = '';
|
||||||
var content = '';
|
var content = '';
|
||||||
var address = '';
|
var address = '';
|
||||||
|
var profileImageUrl = '';
|
||||||
|
var profileName = '';
|
||||||
|
|
||||||
switch (parsedAddress.parseFrom) {
|
switch (parsedAddress.parseFrom) {
|
||||||
case ParseFrom.unstoppableDomains:
|
case ParseFrom.unstoppableDomains:
|
||||||
|
@ -37,11 +39,15 @@ Future<String> extractAddressFromParsed(
|
||||||
title = S.of(context).address_detected;
|
title = S.of(context).address_detected;
|
||||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Twitter)');
|
content = S.of(context).extracted_address_content('${parsedAddress.name} (Twitter)');
|
||||||
address = parsedAddress.addresses.first;
|
address = parsedAddress.addresses.first;
|
||||||
|
profileImageUrl = parsedAddress.profileImageUrl;
|
||||||
|
profileName = parsedAddress.profileName;
|
||||||
break;
|
break;
|
||||||
case ParseFrom.mastodon:
|
case ParseFrom.mastodon:
|
||||||
title = S.of(context).address_detected;
|
title = S.of(context).address_detected;
|
||||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Mastodon)');
|
content = S.of(context).extracted_address_content('${parsedAddress.name} (Mastodon)');
|
||||||
address = parsedAddress.addresses.first;
|
address = parsedAddress.addresses.first;
|
||||||
|
profileImageUrl = parsedAddress.profileImageUrl;
|
||||||
|
profileName = parsedAddress.profileName;
|
||||||
break;
|
break;
|
||||||
case ParseFrom.nostr:
|
case ParseFrom.nostr:
|
||||||
title = S.of(context).address_detected;
|
title = S.of(context).address_detected;
|
||||||
|
@ -95,6 +101,8 @@ Future<String> extractAddressFromParsed(
|
||||||
|
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
alertTitle: title,
|
alertTitle: title,
|
||||||
|
headerTitleText: profileName.isEmpty ? null : profileName,
|
||||||
|
headerImageProfileUrl: profileImageUrl.isEmpty ? null : profileImageUrl,
|
||||||
alertContent: content,
|
alertContent: content,
|
||||||
buttonText: S.of(context).ok,
|
buttonText: S.of(context).ok,
|
||||||
buttonAction: () => Navigator.of(context).pop());
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
|
|
|
@ -7,7 +7,9 @@ class AlertWithOneAction extends BaseAlertDialog {
|
||||||
required this.alertContent,
|
required this.alertContent,
|
||||||
required this.buttonText,
|
required this.buttonText,
|
||||||
required this.buttonAction,
|
required this.buttonAction,
|
||||||
this.alertBarrierDismissible = true
|
this.alertBarrierDismissible = true,
|
||||||
|
this.headerTitleText,
|
||||||
|
this.headerImageProfileUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
final String alertTitle;
|
final String alertTitle;
|
||||||
|
@ -15,6 +17,8 @@ class AlertWithOneAction extends BaseAlertDialog {
|
||||||
final String buttonText;
|
final String buttonText;
|
||||||
final VoidCallback buttonAction;
|
final VoidCallback buttonAction;
|
||||||
final bool alertBarrierDismissible;
|
final bool alertBarrierDismissible;
|
||||||
|
final String? headerTitleText;
|
||||||
|
final String? headerImageProfileUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get titleText => alertTitle;
|
String get titleText => alertTitle;
|
||||||
|
@ -25,6 +29,12 @@ class AlertWithOneAction extends BaseAlertDialog {
|
||||||
@override
|
@override
|
||||||
bool get barrierDismissible => alertBarrierDismissible;
|
bool get barrierDismissible => alertBarrierDismissible;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get headerImageUrl => headerImageProfileUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get headerText => headerTitleText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget actionButtons(BuildContext context) {
|
Widget actionButtons(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||||
|
@ -5,19 +6,34 @@ import 'package:cake_wallet/themes/extensions/alert_theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BaseAlertDialog extends StatelessWidget {
|
class BaseAlertDialog extends StatelessWidget {
|
||||||
|
String? get headerText => '';
|
||||||
|
|
||||||
String get titleText => '';
|
String get titleText => '';
|
||||||
|
|
||||||
String get contentText => '';
|
String get contentText => '';
|
||||||
|
|
||||||
String get leftActionButtonText => '';
|
String get leftActionButtonText => '';
|
||||||
|
|
||||||
String get rightActionButtonText => '';
|
String get rightActionButtonText => '';
|
||||||
|
|
||||||
bool get isDividerExists => false;
|
bool get isDividerExists => false;
|
||||||
|
|
||||||
VoidCallback get actionLeft => () {};
|
VoidCallback get actionLeft => () {};
|
||||||
|
|
||||||
VoidCallback get actionRight => () {};
|
VoidCallback get actionRight => () {};
|
||||||
|
|
||||||
bool get barrierDismissible => true;
|
bool get barrierDismissible => true;
|
||||||
|
|
||||||
Color? get leftActionButtonTextColor => null;
|
Color? get leftActionButtonTextColor => null;
|
||||||
|
|
||||||
Color? get rightActionButtonTextColor => null;
|
Color? get rightActionButtonTextColor => null;
|
||||||
|
|
||||||
Color? get leftActionButtonColor => null;
|
Color? get leftActionButtonColor => null;
|
||||||
|
|
||||||
Color? get rightActionButtonColor => null;
|
Color? get rightActionButtonColor => null;
|
||||||
|
|
||||||
|
String? get headerImageUrl => null;
|
||||||
|
|
||||||
Widget title(BuildContext context) {
|
Widget title(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
titleText,
|
titleText,
|
||||||
|
@ -32,6 +48,23 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget headerTitle(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 10.0),
|
||||||
|
child: Text(
|
||||||
|
headerText!,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 25,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget content(BuildContext context) {
|
Widget content(BuildContext context) {
|
||||||
return Text(
|
return Text(
|
||||||
contentText,
|
contentText,
|
||||||
|
@ -48,17 +81,17 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
|
|
||||||
Widget actionButtons(BuildContext context) {
|
Widget actionButtons(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 60,
|
height: 60,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: actionLeft,
|
onPressed: actionLeft,
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor: leftActionButtonColor ??
|
backgroundColor:
|
||||||
Theme.of(context).dialogBackgroundColor,
|
leftActionButtonColor ?? Theme.of(context).dialogBackgroundColor,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.zero))),
|
borderRadius: BorderRadius.all(Radius.zero))),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
@ -79,8 +112,8 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: actionRight,
|
onPressed: actionRight,
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor: rightActionButtonColor ??
|
backgroundColor:
|
||||||
Theme.of(context).dialogBackgroundColor,
|
rightActionButtonColor ?? Theme.of(context).dialogBackgroundColor,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.zero))),
|
borderRadius: BorderRadius.all(Radius.zero))),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
@ -90,8 +123,7 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: rightActionButtonTextColor ??
|
color: rightActionButtonTextColor ?? Theme.of(context).primaryColor,
|
||||||
Theme.of(context).primaryColor,
|
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
@ -100,6 +132,24 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget headerImage(BuildContext context, String imageUrl) {
|
||||||
|
return Positioned(
|
||||||
|
top: -50,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 50,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
child: ClipOval(
|
||||||
|
child: Image.network(
|
||||||
|
imageUrl,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
@ -109,43 +159,51 @@ class BaseAlertDialog extends StatelessWidget {
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration:
|
||||||
color:
|
BoxDecoration(color: Theme.of(context).extension<AlertTheme>()!.backdropColor),
|
||||||
Theme.of(context).extension<AlertTheme>()!.backdropColor),
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => null,
|
onTap: () => null,
|
||||||
child: ClipRRect(
|
child: Container(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
decoration: BoxDecoration(
|
||||||
child: Container(
|
borderRadius: BorderRadius.circular(30),
|
||||||
width: 300,
|
color: Theme.of(context).dialogBackgroundColor),
|
||||||
color: Theme.of(context).dialogBackgroundColor,
|
width: 300,
|
||||||
child: Column(
|
child: Stack(
|
||||||
mainAxisSize: MainAxisSize.min,
|
clipBehavior: Clip.none,
|
||||||
children: <Widget>[
|
children: [
|
||||||
Column(
|
if (headerImageUrl != null) headerImage(context, headerImageUrl!),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Column(
|
||||||
children: <Widget>[
|
mainAxisSize: MainAxisSize.min,
|
||||||
Padding(
|
children: <Widget>[
|
||||||
padding: EdgeInsets.fromLTRB(24, 20, 24, 0),
|
if (headerImageUrl != null) const SizedBox(height: 50),
|
||||||
child: title(context),
|
Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
isDividerExists
|
children: <Widget>[
|
||||||
? Padding(
|
if (headerText != null) headerTitle(context),
|
||||||
padding: EdgeInsets.only(top: 16, bottom: 8),
|
Padding(
|
||||||
child: const HorizontalSectionDivider(),
|
padding: EdgeInsets.fromLTRB(24, 20, 24, 0),
|
||||||
)
|
child: title(context),
|
||||||
: Offstage(),
|
),
|
||||||
Padding(
|
isDividerExists
|
||||||
padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
|
? Padding(
|
||||||
child: content(context),
|
padding: EdgeInsets.only(top: 16, bottom: 8),
|
||||||
)
|
child: const HorizontalSectionDivider(),
|
||||||
],
|
)
|
||||||
),
|
: Offstage(),
|
||||||
const HorizontalSectionDivider(),
|
Padding(
|
||||||
actionButtons(context)
|
padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
|
||||||
],
|
child: content(context),
|
||||||
),
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const HorizontalSectionDivider(),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||||
|
child: actionButtons(context))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -12,7 +12,7 @@ class TwitterApi {
|
||||||
|
|
||||||
static Future<TwitterUser> lookupUserByName({required String userName}) async {
|
static Future<TwitterUser> lookupUserByName({required String userName}) async {
|
||||||
final queryParams = {
|
final queryParams = {
|
||||||
'user.fields': 'description',
|
'user.fields': 'description,profile_image_url',
|
||||||
'expansions': 'pinned_tweet_id',
|
'expansions': 'pinned_tweet_id',
|
||||||
'tweet.fields': 'note_tweet'
|
'tweet.fields': 'note_tweet'
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,20 +4,25 @@ class TwitterUser {
|
||||||
required this.username,
|
required this.username,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.description,
|
required this.description,
|
||||||
|
required this.profileImageUrl,
|
||||||
this.pinnedTweet});
|
this.pinnedTweet});
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String username;
|
final String username;
|
||||||
final String name;
|
final String name;
|
||||||
final String description;
|
final String description;
|
||||||
|
final String profileImageUrl;
|
||||||
final Tweet? pinnedTweet;
|
final Tweet? pinnedTweet;
|
||||||
|
|
||||||
factory TwitterUser.fromJson(Map<String, dynamic> json, [Tweet? pinnedTweet]) {
|
factory TwitterUser.fromJson(Map<String, dynamic> json, [Tweet? pinnedTweet]) {
|
||||||
|
final profileImageUrl = json['data']['profile_image_url'] as String? ?? '';
|
||||||
|
final scaledProfileImageUrl = profileImageUrl.replaceFirst('normal', '200x200');
|
||||||
return TwitterUser(
|
return TwitterUser(
|
||||||
id: json['data']['id'] as String,
|
id: json['data']['id'] as String,
|
||||||
username: json['data']['username'] as String,
|
username: json['data']['username'] as String? ?? '',
|
||||||
name: json['data']['name'] as String,
|
name: json['data']['name'] as String,
|
||||||
description: json['data']['description'] as String? ?? '',
|
description: json['data']['description'] as String? ?? '',
|
||||||
|
profileImageUrl: scaledProfileImageUrl,
|
||||||
pinnedTweet: pinnedTweet,
|
pinnedTweet: pinnedTweet,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue