mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 03:42:13 +00:00
handle scanned qr link via intent
This commit is contained in:
parent
7d09bd7283
commit
95c5d6a4f1
11 changed files with 245 additions and 162 deletions
13
lib/app.dart
13
lib/app.dart
|
|
@ -113,7 +113,6 @@ class AppMainWidget extends StatefulWidget {
|
|||
|
||||
class _AppMainWidgetState extends State<AppMainWidget> {
|
||||
bool _isUserCreated = false;
|
||||
bool _showDatabaseMigration = false;
|
||||
bool _showOnboarding = true;
|
||||
bool _isLoaded = false;
|
||||
bool _skipBackup = false;
|
||||
|
|
@ -135,18 +134,12 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
// do not change in case twonly was already unlocked at some point
|
||||
_isTwonlyLocked = userService.currentUser.screenLockEnabled;
|
||||
}
|
||||
if (userService.currentUser.appVersion < 62) {
|
||||
_showDatabaseMigration = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_isUserCreated && !_showDatabaseMigration) {
|
||||
} else {
|
||||
// This means the user is in the onboarding screen, so start with the Proof of Work.
|
||||
|
||||
final (proof, disabled) = await apiService.getProofOfWork();
|
||||
if (proof != null) {
|
||||
Log.info('Starting with proof of work calculation.');
|
||||
// Starting with the proof of work.
|
||||
_proofOfWork = (
|
||||
calculatePoW(proof.prefix, proof.difficulty.toInt()),
|
||||
false,
|
||||
|
|
@ -169,9 +162,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
|
||||
late Widget child;
|
||||
|
||||
if (_showDatabaseMigration) {
|
||||
child = const Center(child: Text('Please reinstall twonly.'));
|
||||
} else if (_isUserCreated) {
|
||||
if (_isUserCreated) {
|
||||
if (_isTwonlyLocked) {
|
||||
child = UnlockTwonlyView(
|
||||
callbackOnSuccess: () => setState(() {
|
||||
|
|
|
|||
|
|
@ -99,21 +99,6 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
|||
)..where((t) => t.userId.equals(userid))).watchSingleOrNull();
|
||||
}
|
||||
|
||||
Stream<(Contact, bool)?> watchContactAndVerificationState(int userid) {
|
||||
final query = (select(contacts)..where((t) => t.userId.equals(userid))).join([
|
||||
leftOuterJoin(
|
||||
keyVerifications,
|
||||
keyVerifications.contactId.equalsExp(contacts.userId),
|
||||
),
|
||||
]);
|
||||
return query
|
||||
.map((row) => (
|
||||
row.readTable(contacts),
|
||||
row.readTableOrNull(keyVerifications) != null,
|
||||
))
|
||||
.watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<List<Contact>> getAllContacts() {
|
||||
return select(contacts).get();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,18 +47,14 @@ enum VerificationType {
|
|||
|
||||
@DataClassName('KeyVerification')
|
||||
class KeyVerifications extends Table {
|
||||
IntColumn get verificationId => integer().autoIncrement()();
|
||||
IntColumn get contactId => integer().references(
|
||||
Contacts,
|
||||
#userId,
|
||||
onDelete: KeyAction.cascade,
|
||||
)();
|
||||
|
||||
TextColumn get type => textEnum<VerificationType>()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {contactId};
|
||||
}
|
||||
|
||||
@DataClassName('VerificationToken')
|
||||
|
|
|
|||
|
|
@ -9041,6 +9041,21 @@ class $KeyVerificationsTable extends KeyVerifications
|
|||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
$KeyVerificationsTable(this.attachedDatabase, [this._alias]);
|
||||
static const VerificationMeta _verificationIdMeta = const VerificationMeta(
|
||||
'verificationId',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<int> verificationId = GeneratedColumn<int>(
|
||||
'verification_id',
|
||||
aliasedName,
|
||||
false,
|
||||
hasAutoIncrement: true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'PRIMARY KEY AUTOINCREMENT',
|
||||
),
|
||||
);
|
||||
static const VerificationMeta _contactIdMeta = const VerificationMeta(
|
||||
'contactId',
|
||||
);
|
||||
|
|
@ -9050,7 +9065,7 @@ class $KeyVerificationsTable extends KeyVerifications
|
|||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES contacts (user_id) ON DELETE CASCADE',
|
||||
),
|
||||
|
|
@ -9077,7 +9092,12 @@ class $KeyVerificationsTable extends KeyVerifications
|
|||
defaultValue: currentDateAndTime,
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [contactId, type, createdAt];
|
||||
List<GeneratedColumn> get $columns => [
|
||||
verificationId,
|
||||
contactId,
|
||||
type,
|
||||
createdAt,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
|
|
@ -9090,11 +9110,22 @@ class $KeyVerificationsTable extends KeyVerifications
|
|||
}) {
|
||||
final context = VerificationContext();
|
||||
final data = instance.toColumns(true);
|
||||
if (data.containsKey('verification_id')) {
|
||||
context.handle(
|
||||
_verificationIdMeta,
|
||||
verificationId.isAcceptableOrUnknown(
|
||||
data['verification_id']!,
|
||||
_verificationIdMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('contact_id')) {
|
||||
context.handle(
|
||||
_contactIdMeta,
|
||||
contactId.isAcceptableOrUnknown(data['contact_id']!, _contactIdMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_contactIdMeta);
|
||||
}
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(
|
||||
|
|
@ -9106,11 +9137,15 @@ class $KeyVerificationsTable extends KeyVerifications
|
|||
}
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {contactId};
|
||||
Set<GeneratedColumn> get $primaryKey => {verificationId};
|
||||
@override
|
||||
KeyVerification map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return KeyVerification(
|
||||
verificationId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}verification_id'],
|
||||
)!,
|
||||
contactId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}contact_id'],
|
||||
|
|
@ -9138,10 +9173,12 @@ class $KeyVerificationsTable extends KeyVerifications
|
|||
}
|
||||
|
||||
class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
||||
final int verificationId;
|
||||
final int contactId;
|
||||
final VerificationType type;
|
||||
final DateTime createdAt;
|
||||
const KeyVerification({
|
||||
required this.verificationId,
|
||||
required this.contactId,
|
||||
required this.type,
|
||||
required this.createdAt,
|
||||
|
|
@ -9149,6 +9186,7 @@ class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
|||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['verification_id'] = Variable<int>(verificationId);
|
||||
map['contact_id'] = Variable<int>(contactId);
|
||||
{
|
||||
map['type'] = Variable<String>(
|
||||
|
|
@ -9161,6 +9199,7 @@ class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
|||
|
||||
KeyVerificationsCompanion toCompanion(bool nullToAbsent) {
|
||||
return KeyVerificationsCompanion(
|
||||
verificationId: Value(verificationId),
|
||||
contactId: Value(contactId),
|
||||
type: Value(type),
|
||||
createdAt: Value(createdAt),
|
||||
|
|
@ -9173,6 +9212,7 @@ class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
|||
}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return KeyVerification(
|
||||
verificationId: serializer.fromJson<int>(json['verificationId']),
|
||||
contactId: serializer.fromJson<int>(json['contactId']),
|
||||
type: $KeyVerificationsTable.$convertertype.fromJson(
|
||||
serializer.fromJson<String>(json['type']),
|
||||
|
|
@ -9184,6 +9224,7 @@ class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
|||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'verificationId': serializer.toJson<int>(verificationId),
|
||||
'contactId': serializer.toJson<int>(contactId),
|
||||
'type': serializer.toJson<String>(
|
||||
$KeyVerificationsTable.$convertertype.toJson(type),
|
||||
|
|
@ -9193,16 +9234,21 @@ class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
|||
}
|
||||
|
||||
KeyVerification copyWith({
|
||||
int? verificationId,
|
||||
int? contactId,
|
||||
VerificationType? type,
|
||||
DateTime? createdAt,
|
||||
}) => KeyVerification(
|
||||
verificationId: verificationId ?? this.verificationId,
|
||||
contactId: contactId ?? this.contactId,
|
||||
type: type ?? this.type,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
KeyVerification copyWithCompanion(KeyVerificationsCompanion data) {
|
||||
return KeyVerification(
|
||||
verificationId: data.verificationId.present
|
||||
? data.verificationId.value
|
||||
: this.verificationId,
|
||||
contactId: data.contactId.present ? data.contactId.value : this.contactId,
|
||||
type: data.type.present ? data.type.value : this.type,
|
||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||
|
|
@ -9212,6 +9258,7 @@ class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
|||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('KeyVerification(')
|
||||
..write('verificationId: $verificationId, ')
|
||||
..write('contactId: $contactId, ')
|
||||
..write('type: $type, ')
|
||||
..write('createdAt: $createdAt')
|
||||
|
|
@ -9220,36 +9267,43 @@ class KeyVerification extends DataClass implements Insertable<KeyVerification> {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(contactId, type, createdAt);
|
||||
int get hashCode => Object.hash(verificationId, contactId, type, createdAt);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is KeyVerification &&
|
||||
other.verificationId == this.verificationId &&
|
||||
other.contactId == this.contactId &&
|
||||
other.type == this.type &&
|
||||
other.createdAt == this.createdAt);
|
||||
}
|
||||
|
||||
class KeyVerificationsCompanion extends UpdateCompanion<KeyVerification> {
|
||||
final Value<int> verificationId;
|
||||
final Value<int> contactId;
|
||||
final Value<VerificationType> type;
|
||||
final Value<DateTime> createdAt;
|
||||
const KeyVerificationsCompanion({
|
||||
this.verificationId = const Value.absent(),
|
||||
this.contactId = const Value.absent(),
|
||||
this.type = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
});
|
||||
KeyVerificationsCompanion.insert({
|
||||
this.contactId = const Value.absent(),
|
||||
this.verificationId = const Value.absent(),
|
||||
required int contactId,
|
||||
required VerificationType type,
|
||||
this.createdAt = const Value.absent(),
|
||||
}) : type = Value(type);
|
||||
}) : contactId = Value(contactId),
|
||||
type = Value(type);
|
||||
static Insertable<KeyVerification> custom({
|
||||
Expression<int>? verificationId,
|
||||
Expression<int>? contactId,
|
||||
Expression<String>? type,
|
||||
Expression<DateTime>? createdAt,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (verificationId != null) 'verification_id': verificationId,
|
||||
if (contactId != null) 'contact_id': contactId,
|
||||
if (type != null) 'type': type,
|
||||
if (createdAt != null) 'created_at': createdAt,
|
||||
|
|
@ -9257,11 +9311,13 @@ class KeyVerificationsCompanion extends UpdateCompanion<KeyVerification> {
|
|||
}
|
||||
|
||||
KeyVerificationsCompanion copyWith({
|
||||
Value<int>? verificationId,
|
||||
Value<int>? contactId,
|
||||
Value<VerificationType>? type,
|
||||
Value<DateTime>? createdAt,
|
||||
}) {
|
||||
return KeyVerificationsCompanion(
|
||||
verificationId: verificationId ?? this.verificationId,
|
||||
contactId: contactId ?? this.contactId,
|
||||
type: type ?? this.type,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
|
|
@ -9271,6 +9327,9 @@ class KeyVerificationsCompanion extends UpdateCompanion<KeyVerification> {
|
|||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (verificationId.present) {
|
||||
map['verification_id'] = Variable<int>(verificationId.value);
|
||||
}
|
||||
if (contactId.present) {
|
||||
map['contact_id'] = Variable<int>(contactId.value);
|
||||
}
|
||||
|
|
@ -9288,6 +9347,7 @@ class KeyVerificationsCompanion extends UpdateCompanion<KeyVerification> {
|
|||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('KeyVerificationsCompanion(')
|
||||
..write('verificationId: $verificationId, ')
|
||||
..write('contactId: $contactId, ')
|
||||
..write('type: $type, ')
|
||||
..write('createdAt: $createdAt')
|
||||
|
|
@ -19473,12 +19533,14 @@ typedef $$GroupHistoriesTableProcessedTableManager =
|
|||
>;
|
||||
typedef $$KeyVerificationsTableCreateCompanionBuilder =
|
||||
KeyVerificationsCompanion Function({
|
||||
Value<int> contactId,
|
||||
Value<int> verificationId,
|
||||
required int contactId,
|
||||
required VerificationType type,
|
||||
Value<DateTime> createdAt,
|
||||
});
|
||||
typedef $$KeyVerificationsTableUpdateCompanionBuilder =
|
||||
KeyVerificationsCompanion Function({
|
||||
Value<int> verificationId,
|
||||
Value<int> contactId,
|
||||
Value<VerificationType> type,
|
||||
Value<DateTime> createdAt,
|
||||
|
|
@ -19522,6 +19584,11 @@ class $$KeyVerificationsTableFilterComposer
|
|||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<int> get verificationId => $composableBuilder(
|
||||
column: $table.verificationId,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnWithTypeConverterFilters<VerificationType, VerificationType, String>
|
||||
get type => $composableBuilder(
|
||||
column: $table.type,
|
||||
|
|
@ -19566,6 +19633,11 @@ class $$KeyVerificationsTableOrderingComposer
|
|||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<int> get verificationId => $composableBuilder(
|
||||
column: $table.verificationId,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get type => $composableBuilder(
|
||||
column: $table.type,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
|
|
@ -19609,6 +19681,11 @@ class $$KeyVerificationsTableAnnotationComposer
|
|||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<int> get verificationId => $composableBuilder(
|
||||
column: $table.verificationId,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
GeneratedColumnWithTypeConverter<VerificationType, String> get type =>
|
||||
$composableBuilder(column: $table.type, builder: (column) => column);
|
||||
|
||||
|
|
@ -19669,20 +19746,24 @@ class $$KeyVerificationsTableTableManager
|
|||
$$KeyVerificationsTableAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback:
|
||||
({
|
||||
Value<int> verificationId = const Value.absent(),
|
||||
Value<int> contactId = const Value.absent(),
|
||||
Value<VerificationType> type = const Value.absent(),
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) => KeyVerificationsCompanion(
|
||||
verificationId: verificationId,
|
||||
contactId: contactId,
|
||||
type: type,
|
||||
createdAt: createdAt,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
Value<int> contactId = const Value.absent(),
|
||||
Value<int> verificationId = const Value.absent(),
|
||||
required int contactId,
|
||||
required VerificationType type,
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
}) => KeyVerificationsCompanion.insert(
|
||||
verificationId: verificationId,
|
||||
contactId: contactId,
|
||||
type: type,
|
||||
createdAt: createdAt,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
|||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/qr.utils.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart';
|
||||
import 'package:twonly/src/visual/views/chats/add_new_user.view.dart';
|
||||
|
|
@ -25,6 +26,41 @@ Future<bool> handleIntentUrl(BuildContext context, Uri uri) async {
|
|||
if (uri.host != 'me.twonly.eu') return false;
|
||||
if (uri.hasEmptyPath) return false;
|
||||
|
||||
// Check if this is the QR code link which was
|
||||
// therefore scanned with the system camera
|
||||
|
||||
if (uri.toString().startsWith(QrCodeUtils.linkPrefix)) {
|
||||
final result = await QrCodeUtils.handleQrCodeLink(uri.toString());
|
||||
|
||||
if (!context.mounted) return false;
|
||||
|
||||
if (result != null) {
|
||||
final (profile, contact, verificationOk) = result;
|
||||
|
||||
if (profile.username == userService.currentUser.username) {
|
||||
await context.push(Routes.settingsPublicProfile);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contact != null) {
|
||||
if (verificationOk) {
|
||||
await context.push(Routes.profileContact(contact.userId));
|
||||
} else {
|
||||
await _pubKeysDoNotMatch(context, contact.username);
|
||||
}
|
||||
} else {
|
||||
await context.navPush(
|
||||
AddNewUserView(
|
||||
username: profile.username,
|
||||
publicKey: Uint8List.fromList(profile.publicIdentityKey),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
final publicKey = uri.hasFragment ? uri.fragment : null;
|
||||
final userPaths = uri.path.split('/');
|
||||
if (userPaths.length != 2) return false;
|
||||
|
|
@ -40,25 +76,26 @@ Future<bool> handleIntentUrl(BuildContext context, Uri uri) async {
|
|||
Log.info(
|
||||
'Opened via deep link!: username = $username public_key = ${uri.fragment}',
|
||||
);
|
||||
|
||||
final contacts = await twonlyDB.contactsDao.getContactsByUsername(username);
|
||||
if (contacts.isEmpty) {
|
||||
if (!context.mounted) return true;
|
||||
|
||||
if (contacts.isEmpty) {
|
||||
// User does not yet exists, making a request...
|
||||
Uint8List? publicKeyBytes;
|
||||
if (publicKey != null) {
|
||||
publicKeyBytes = base64Url.decode(publicKey);
|
||||
}
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return AddNewUserView(
|
||||
await context.navPush(
|
||||
AddNewUserView(
|
||||
username: username,
|
||||
publicKey: publicKeyBytes,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (publicKey != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (publicKey != null) {
|
||||
try {
|
||||
final contact = contacts.first;
|
||||
final storedPublicKey = await getPublicKeyFromContact(contact.userId);
|
||||
|
|
@ -85,20 +122,25 @@ Future<bool> handleIntentUrl(BuildContext context, Uri uri) async {
|
|||
await context.push(Routes.profileContact(contact.userId));
|
||||
}
|
||||
} else {
|
||||
await showAlertDialog(
|
||||
context,
|
||||
context.lang.couldNotVerifyUsername(contact.username),
|
||||
context.lang.linkPubkeyDoesNotMatch,
|
||||
customCancel: '',
|
||||
);
|
||||
await _pubKeysDoNotMatch(context, contact.username);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _pubKeysDoNotMatch(BuildContext context, String username) async {
|
||||
await showAlertDialog(
|
||||
context,
|
||||
context.lang.couldNotVerifyUsername(username),
|
||||
context.lang.linkPubkeyDoesNotMatch,
|
||||
customCancel: '',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> handleIntentMediaFile(
|
||||
BuildContext context,
|
||||
String filePath,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
|||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/qr.dart';
|
||||
import 'package:twonly/src/utils/qr.utils.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart';
|
||||
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
||||
|
|
@ -377,7 +377,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
// shouldReturn is null when the user used the back button
|
||||
if (shouldReturn != null && shouldReturn) {
|
||||
if (widget.sendToGroup == null) {
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
HomeViewState.streamHomeViewPageIndex.add(0);
|
||||
} else if (mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
|||
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/qr.dart';
|
||||
import 'package:twonly/src/utils/qr.utils.dart';
|
||||
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart';
|
||||
|
|
@ -45,7 +45,7 @@ class MainCameraController {
|
|||
bool initCameraStarted = true;
|
||||
Map<int, ScannedVerifiedContact> contactsVerified = {};
|
||||
Map<int, ScannedNewProfile> scannedNewProfiles = {};
|
||||
Set<String> _handledProfileLinks = {};
|
||||
final Set<String> _handledProfileLinks = {};
|
||||
String? scannedUrl;
|
||||
GlobalKey zoomButtonKey = GlobalKey();
|
||||
GlobalKey cameraPreviewKey = GlobalKey();
|
||||
|
|
@ -381,6 +381,7 @@ class MainCameraController {
|
|||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (link.startsWith('http://') || link.startsWith('https://')) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
|
|
@ -35,20 +35,18 @@ class _ContactViewState extends State<ContactView> {
|
|||
List<GroupMember> _memberOfGroups = [];
|
||||
List<KeyVerification> _keyVerifications = [];
|
||||
|
||||
late StreamSubscription<(Contact, bool)?> _contactSub;
|
||||
late StreamSubscription<Contact?> _contactSub;
|
||||
late StreamSubscription<List<GroupMember>> _groupMemberSub;
|
||||
late StreamSubscription<List<KeyVerification>> _streamKeyVerifications;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_contactSub = twonlyDB.contactsDao
|
||||
.watchContactAndVerificationState(widget.userId)
|
||||
.listen((
|
||||
_contactSub = twonlyDB.contactsDao.watchContact(widget.userId).listen((
|
||||
update,
|
||||
) {
|
||||
if (update != null) {
|
||||
setState(() {
|
||||
_contact = update.$1;
|
||||
_contact = update;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -250,7 +248,7 @@ class _ContactViewState extends State<ContactView> {
|
|||
backgroundColor: context.color.surfaceContainer,
|
||||
collapsedShape: const RoundedRectangleBorder(),
|
||||
leading: Padding(
|
||||
padding: EdgeInsetsGeometry.only(left: 12, right: 12),
|
||||
padding: const EdgeInsetsGeometry.only(left: 12, right: 12),
|
||||
child: VerificationBadgeComp(
|
||||
contact: contact,
|
||||
size: 20,
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart';
|
|||
import 'package:twonly/src/visual/views/chats/chat_list.view.dart';
|
||||
import 'package:twonly/src/visual/views/memories/memories.view.dart';
|
||||
|
||||
void Function(int) globalUpdateOfHomeViewPageIndex = (a) {};
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
const HomeView({
|
||||
required this.initialPage,
|
||||
|
|
@ -32,68 +30,19 @@ class HomeView extends StatefulWidget {
|
|||
State<HomeView> createState() => HomeViewState();
|
||||
}
|
||||
|
||||
class Shade extends StatelessWidget {
|
||||
const Shade({required this.opacity, super.key});
|
||||
final double opacity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned.fill(
|
||||
child: Opacity(
|
||||
opacity: opacity,
|
||||
child: Container(
|
||||
color: context.color.surface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeViewState extends State<HomeView> {
|
||||
int _activePageIdx = 1;
|
||||
double _offsetRatio = 0;
|
||||
double _offsetFromOne = 0;
|
||||
Timer? _disableCameraTimer;
|
||||
|
||||
final MainCameraController _mainCameraController = MainCameraController();
|
||||
|
||||
final PageController _homeViewPageController = PageController(initialPage: 1);
|
||||
|
||||
late StreamSubscription<List<SharedFile>> _intentStreamSub;
|
||||
late StreamSubscription<Uri> _deepLinkSub;
|
||||
|
||||
double buttonDiameter = 100;
|
||||
double offsetRatio = 0;
|
||||
double offsetFromOne = 0;
|
||||
double lastChange = 0;
|
||||
|
||||
Timer? disableCameraTimer;
|
||||
|
||||
bool onPageView(ScrollNotification notification) {
|
||||
disableCameraTimer?.cancel();
|
||||
if (notification.depth == 0 && notification is ScrollUpdateNotification) {
|
||||
final page = _homeViewPageController.page ?? 0;
|
||||
lastChange = page;
|
||||
setState(() {
|
||||
offsetFromOne = 1.0 - (_homeViewPageController.page ?? 0);
|
||||
offsetRatio = offsetFromOne.abs();
|
||||
});
|
||||
}
|
||||
if (_mainCameraController.cameraController == null &&
|
||||
!_mainCameraController.initCameraStarted &&
|
||||
offsetRatio < 1) {
|
||||
unawaited(
|
||||
_mainCameraController.selectCamera(
|
||||
_mainCameraController.selectedCameraDetails.cameraId,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (offsetRatio == 1) {
|
||||
disableCameraTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||
await _mainCameraController.closeCamera();
|
||||
_mainCameraController.sharedLinkForPreview = null;
|
||||
disableCameraTimer = null;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static final streamHomeViewPageIndex = StreamController<int>.broadcast();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -102,59 +51,51 @@ class HomeViewState extends State<HomeView> {
|
|||
if (mounted) setState(() {});
|
||||
};
|
||||
|
||||
globalUpdateOfHomeViewPageIndex = (index) {
|
||||
streamHomeViewPageIndex.stream.listen((index) {
|
||||
_homeViewPageController.jumpToPage(index);
|
||||
setState(() {
|
||||
_activePageIdx = index;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
selectNotificationStream.stream.listen((response) async {
|
||||
if (response.payload != null &&
|
||||
response.payload!.startsWith(Routes.chats) &&
|
||||
response.payload! != Routes.chats) {
|
||||
await routerProvider.push(response.payload!);
|
||||
}
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
streamHomeViewPageIndex.add(0);
|
||||
});
|
||||
|
||||
unawaited(_mainCameraController.selectCamera(0, true));
|
||||
unawaited(initAsync());
|
||||
unawaited(_initAsync());
|
||||
|
||||
// Subscribe to all events (initial link and further)
|
||||
_deepLinkSub = AppLinks().uriLinkStream.listen((uri) async {
|
||||
if (mounted) {
|
||||
if (!mounted) return;
|
||||
Log.info('Got link via app links: ${uri.scheme}');
|
||||
if (!await handleIntentUrl(context, uri)) {
|
||||
if (uri.scheme.startsWith('http')) {
|
||||
_mainCameraController.setSharedLinkForPreview(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_intentStreamSub = initIntentStreams(
|
||||
context,
|
||||
_mainCameraController.setSharedLinkForPreview,
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.initialPage == 1 &&
|
||||
!userService.currentUser.startWithCameraOpen ||
|
||||
widget.initialPage == 0) {
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
streamHomeViewPageIndex.add(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unawaited(selectNotificationStream.close());
|
||||
disableCameraTimer?.cancel();
|
||||
_mainCameraController.closeCamera();
|
||||
_intentStreamSub.cancel();
|
||||
_deepLinkSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
Future<void> _initAsync() async {
|
||||
final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin
|
||||
.getNotificationAppLaunchDetails();
|
||||
|
||||
|
|
@ -168,10 +109,10 @@ class HomeViewState extends State<HomeView> {
|
|||
payload.startsWith(Routes.chats) &&
|
||||
payload != Routes.chats) {
|
||||
await routerProvider.push(payload);
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
streamHomeViewPageIndex.add(0);
|
||||
}
|
||||
if (payload == Routes.chats) {
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
streamHomeViewPageIndex.add(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -191,22 +132,69 @@ class HomeViewState extends State<HomeView> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
selectNotificationStream.close();
|
||||
streamHomeViewPageIndex.close();
|
||||
_disableCameraTimer?.cancel();
|
||||
_mainCameraController.closeCamera();
|
||||
_intentStreamSub.cancel();
|
||||
_deepLinkSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool _onPageView(ScrollNotification notification) {
|
||||
_disableCameraTimer?.cancel();
|
||||
|
||||
if (notification.depth == 0 && notification is ScrollUpdateNotification) {
|
||||
setState(() {
|
||||
_offsetFromOne = 1.0 - (_homeViewPageController.page ?? 0);
|
||||
_offsetRatio = _offsetFromOne.abs();
|
||||
});
|
||||
}
|
||||
|
||||
if (_mainCameraController.cameraController == null &&
|
||||
!_mainCameraController.initCameraStarted &&
|
||||
_offsetRatio < 1) {
|
||||
unawaited(
|
||||
_mainCameraController.selectCamera(
|
||||
_mainCameraController.selectedCameraDetails.cameraId,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_offsetRatio == 1) {
|
||||
_disableCameraTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||
await _mainCameraController.closeCamera();
|
||||
_mainCameraController.sharedLinkForPreview = null;
|
||||
_disableCameraTimer = null;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: GestureDetector(
|
||||
onDoubleTap: offsetRatio == 0
|
||||
onDoubleTap: _offsetRatio == 0
|
||||
? _mainCameraController.onDoubleTap
|
||||
: null,
|
||||
onTapDown: offsetRatio == 0 ? _mainCameraController.onTapDown : null,
|
||||
onTapDown: _offsetRatio == 0 ? _mainCameraController.onTapDown : null,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
MainCameraPreview(mainCameraController: _mainCameraController),
|
||||
Shade(
|
||||
opacity: offsetRatio,
|
||||
Positioned.fill(
|
||||
child: Opacity(
|
||||
opacity: _offsetRatio,
|
||||
child: Container(
|
||||
color: context.color.surface,
|
||||
),
|
||||
),
|
||||
),
|
||||
NotificationListener<ScrollNotification>(
|
||||
onNotification: onPageView,
|
||||
onNotification: _onPageView,
|
||||
child: Positioned.fill(
|
||||
child: PageView(
|
||||
controller: _homeViewPageController,
|
||||
|
|
@ -227,15 +215,16 @@ class HomeViewState extends State<HomeView> {
|
|||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: (offsetRatio > 0.25)
|
||||
bottom: (_offsetRatio > 0.25)
|
||||
? MediaQuery.sizeOf(context).height * 2
|
||||
: 0,
|
||||
child: Opacity(
|
||||
opacity: 1 - (offsetRatio * 4) % 1,
|
||||
opacity: 1 - (_offsetRatio * 4) % 1,
|
||||
child: CameraPreviewControllerView(
|
||||
mainController: _mainCameraController,
|
||||
isVisible:
|
||||
((1 - (offsetRatio * 4) % 1) == 1) && _activePageIdx == 1,
|
||||
((1 - (_offsetRatio * 4) % 1) == 1) &&
|
||||
_activePageIdx == 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import 'package:twonly/src/constants/routes.keys.dart';
|
|||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
import 'package:twonly/src/utils/avatars.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/qr.dart';
|
||||
import 'package:twonly/src/utils/qr.utils.dart';
|
||||
import 'package:twonly/src/visual/elements/better_list_title.element.dart';
|
||||
|
||||
class PublicProfileView extends StatefulWidget {
|
||||
|
|
|
|||
Loading…
Reference in a new issue