Add initial UI for inventory browser
This commit is contained in:
parent
a823604822
commit
18ce8cf2cb
8 changed files with 300 additions and 21 deletions
|
@ -13,6 +13,13 @@ import 'package:contacts_plus_plus/models/asset/record.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
class AssetApi {
|
class AssetApi {
|
||||||
|
static Future<List<Record>> getRecordsAt(ApiClient client, {required String path}) async {
|
||||||
|
final response = await client.get("/users/${client.userId}/records?path=$path");
|
||||||
|
ApiClient.checkResponse(response);
|
||||||
|
final body = jsonDecode(response.body) as List;
|
||||||
|
return body.map((e) => Record.fromMap(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
static Future<PreprocessStatus> preprocessRecord(ApiClient client, {required Record record}) async {
|
static Future<PreprocessStatus> preprocessRecord(ApiClient client, {required Record record}) async {
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
"/users/${record.ownerId}/records/${record.id}/preprocess", body: jsonEncode(record.toMap()));
|
"/users/${record.ownerId}/records/${record.id}/preprocess", body: jsonEncode(record.toMap()));
|
||||||
|
@ -69,7 +76,7 @@ class AssetApi {
|
||||||
"message_item",
|
"message_item",
|
||||||
"message_id:${Message.generateId()}"
|
"message_id:${Message.generateId()}"
|
||||||
],
|
],
|
||||||
recordType: "texture",
|
recordType: RecordType.texture,
|
||||||
thumbnailUri: assetUri,
|
thumbnailUri: assetUri,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
isForPatreons: false,
|
isForPatreons: false,
|
||||||
|
|
|
@ -88,18 +88,7 @@ class MessagingClient extends ChangeNotifier {
|
||||||
MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient,
|
MessagingClient({required ApiClient apiClient, required NotificationClient notificationClient,
|
||||||
required SettingsClient settingsClient})
|
required SettingsClient settingsClient})
|
||||||
: _apiClient = apiClient, _notificationClient = notificationClient, _settingsClient = settingsClient {
|
: _apiClient = apiClient, _notificationClient = notificationClient, _settingsClient = settingsClient {
|
||||||
initBox().whenComplete(() async {
|
initFriends();
|
||||||
try {
|
|
||||||
await _restoreFriendsList();
|
|
||||||
try {
|
|
||||||
await refreshFriendsList();
|
|
||||||
} catch (_) {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
refreshFriendsListWithErrorHandler();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
startWebsocket();
|
startWebsocket();
|
||||||
_notifyOnlineTimer = Timer.periodic(const Duration(seconds: 60), (timer) async {
|
_notifyOnlineTimer = Timer.periodic(const Duration(seconds: 60), (timer) async {
|
||||||
// We should probably let the MessagingClient handle the entire state of USerStatus instead of mirroring like this
|
// We should probably let the MessagingClient handle the entire state of USerStatus instead of mirroring like this
|
||||||
|
@ -112,6 +101,22 @@ class MessagingClient extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initFriends() async {
|
||||||
|
try {
|
||||||
|
await initBox();
|
||||||
|
await _restoreFriendsList();
|
||||||
|
try {
|
||||||
|
await refreshFriendsList();
|
||||||
|
} catch (e, s) {
|
||||||
|
FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s));
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
} catch (e,s) {
|
||||||
|
FlutterError.reportError(FlutterErrorDetails(exception: e, stack: s));
|
||||||
|
refreshFriendsListWithErrorHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> onSettingsChanged(Settings oldSettings, Settings newSettings) async {
|
Future<void> onSettingsChanged(Settings oldSettings, Settings newSettings) async {
|
||||||
if (oldSettings.notificationsDenied.valueOrDefault != newSettings.notificationsDenied.valueOrDefault) {
|
if (oldSettings.notificationsDenied.valueOrDefault != newSettings.notificationsDenied.valueOrDefault) {
|
||||||
if (newSettings.notificationsDenied.valueOrDefault) {
|
if (newSettings.notificationsDenied.valueOrDefault) {
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
import 'package:contacts_plus_plus/models/asset/neos_db_asset.dart';
|
import 'package:contacts_plus_plus/models/asset/neos_db_asset.dart';
|
||||||
|
import 'package:contacts_plus_plus/string_formatter.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
enum RecordType {
|
||||||
|
unknown,
|
||||||
|
link,
|
||||||
|
object,
|
||||||
|
directory,
|
||||||
|
texture,
|
||||||
|
audio;
|
||||||
|
|
||||||
|
factory RecordType.fromName(String? name) {
|
||||||
|
return RecordType.values.firstWhere((element) => element.name.toLowerCase() == name?.toLowerCase().trim(), orElse: () => RecordType.unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Record {
|
class Record {
|
||||||
final String id;
|
final String id;
|
||||||
final String ownerId;
|
final String ownerId;
|
||||||
|
@ -8,9 +23,9 @@ class Record {
|
||||||
final int globalVersion;
|
final int globalVersion;
|
||||||
final int localVersion;
|
final int localVersion;
|
||||||
final String name;
|
final String name;
|
||||||
|
final TextSpan? formattedName;
|
||||||
final String? description;
|
final String? description;
|
||||||
final List<String>? tags;
|
final List<String>? tags;
|
||||||
final String recordType;
|
|
||||||
final String? thumbnailUri;
|
final String? thumbnailUri;
|
||||||
final bool isPublic;
|
final bool isPublic;
|
||||||
final bool isForPatreons;
|
final bool isForPatreons;
|
||||||
|
@ -21,9 +36,11 @@ class Record {
|
||||||
final String lastModifyingUserId;
|
final String lastModifyingUserId;
|
||||||
final String lastModifyingMachineId;
|
final String lastModifyingMachineId;
|
||||||
final DateTime? creationTime;
|
final DateTime? creationTime;
|
||||||
|
final RecordType recordType;
|
||||||
|
|
||||||
const Record({
|
const Record({
|
||||||
required this.id,
|
required this.id,
|
||||||
|
this.formattedName,
|
||||||
required this.ownerId,
|
required this.ownerId,
|
||||||
this.assetUri,
|
this.assetUri,
|
||||||
this.globalVersion=0,
|
this.globalVersion=0,
|
||||||
|
@ -52,9 +69,10 @@ class Record {
|
||||||
globalVersion: map["globalVersion"] ?? 0,
|
globalVersion: map["globalVersion"] ?? 0,
|
||||||
localVersion: map["localVersion"] ?? 0,
|
localVersion: map["localVersion"] ?? 0,
|
||||||
name: map["name"] ?? "",
|
name: map["name"] ?? "",
|
||||||
|
formattedName: StringFormatter.tryFormat(map["name"]),
|
||||||
description: map["description"],
|
description: map["description"],
|
||||||
tags: (map["tags"] as List).map((e) => e.toString()).toList(),
|
tags: (map["tags"] as List? ?? []).map((e) => e.toString()).toList(),
|
||||||
recordType: map["recordType"] ?? "",
|
recordType: RecordType.fromName(map["recordType"]),
|
||||||
thumbnailUri: map["thumbnailUri"],
|
thumbnailUri: map["thumbnailUri"],
|
||||||
isPublic: map["isPublic"] ?? false,
|
isPublic: map["isPublic"] ?? false,
|
||||||
isForPatreons: map["isForPatreons"] ?? false,
|
isForPatreons: map["isForPatreons"] ?? false,
|
||||||
|
@ -75,9 +93,10 @@ class Record {
|
||||||
int? globalVersion,
|
int? globalVersion,
|
||||||
int? localVersion,
|
int? localVersion,
|
||||||
String? name,
|
String? name,
|
||||||
|
TextSpan? formattedName,
|
||||||
String? description,
|
String? description,
|
||||||
List<String>? tags,
|
List<String>? tags,
|
||||||
String? recordType,
|
RecordType? recordType,
|
||||||
String? thumbnailUri,
|
String? thumbnailUri,
|
||||||
bool? isPublic,
|
bool? isPublic,
|
||||||
bool? isForPatreons,
|
bool? isForPatreons,
|
||||||
|
@ -96,6 +115,7 @@ class Record {
|
||||||
globalVersion: globalVersion ?? this.globalVersion,
|
globalVersion: globalVersion ?? this.globalVersion,
|
||||||
localVersion: localVersion ?? this.localVersion,
|
localVersion: localVersion ?? this.localVersion,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
|
formattedName: formattedName ?? this.formattedName,
|
||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
tags: tags ?? this.tags,
|
tags: tags ?? this.tags,
|
||||||
recordType: recordType ?? this.recordType,
|
recordType: recordType ?? this.recordType,
|
||||||
|
@ -122,7 +142,7 @@ class Record {
|
||||||
"name": name,
|
"name": name,
|
||||||
"description": description,
|
"description": description,
|
||||||
"tags": tags,
|
"tags": tags,
|
||||||
"recordType": recordType,
|
"recordType": recordType.name,
|
||||||
"thumbnailUri": thumbnailUri,
|
"thumbnailUri": thumbnailUri,
|
||||||
"isPublic": isPublic,
|
"isPublic": isPublic,
|
||||||
"isForPatreons": isForPatreons,
|
"isForPatreons": isForPatreons,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:contacts_plus_plus/models/session.dart';
|
||||||
import 'package:contacts_plus_plus/models/user_profile.dart';
|
import 'package:contacts_plus_plus/models/user_profile.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class Friend extends Comparable {
|
class Friend implements Comparable {
|
||||||
final String id;
|
final String id;
|
||||||
final String username;
|
final String username;
|
||||||
final String ownerId;
|
final String ownerId;
|
||||||
|
|
15
lib/models/inventory/neos_path.dart
Normal file
15
lib/models/inventory/neos_path.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:contacts_plus_plus/models/asset/record.dart';
|
||||||
|
|
||||||
|
class NeosPath {
|
||||||
|
final String name;
|
||||||
|
final NeosPath? parent;
|
||||||
|
final List<NeosPath> children;
|
||||||
|
final Record? record;
|
||||||
|
|
||||||
|
const NeosPath({required this.name, required this.parent, required this.children, required this.record});
|
||||||
|
|
||||||
|
String get absolute {
|
||||||
|
if (parent == null) return name;
|
||||||
|
return "${parent!.absolute}\\$name";
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ enum MessageState {
|
||||||
read,
|
read,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Message extends Comparable {
|
class Message implements Comparable {
|
||||||
final String id;
|
final String id;
|
||||||
final String recipientId;
|
final String recipientId;
|
||||||
final String senderId;
|
final String senderId;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import 'package:contacts_plus_plus/client_holder.dart';
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
import 'package:contacts_plus_plus/clients/messaging_client.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/friends/friends_list.dart';
|
import 'package:contacts_plus_plus/widgets/friends/friends_list.dart';
|
||||||
|
import 'package:contacts_plus_plus/widgets/inventory/inventory_browser.dart';
|
||||||
import 'package:contacts_plus_plus/widgets/sessions/sessions_list.dart';
|
import 'package:contacts_plus_plus/widgets/sessions/sessions_list.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -59,7 +60,7 @@ class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin {
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
children: [
|
children: [
|
||||||
const Center(child: Text("Not implemented yet"),),
|
const InventoryBrowser(),
|
||||||
ChangeNotifierProvider
|
ChangeNotifierProvider
|
||||||
.value( // This doesn't need to be a proxy provider since the arguments should never change during it's lifetime.
|
.value( // This doesn't need to be a proxy provider since the arguments should never change during it's lifetime.
|
||||||
value: _mClient,
|
value: _mClient,
|
||||||
|
|
231
lib/widgets/inventory/inventory_browser.dart
Normal file
231
lib/widgets/inventory/inventory_browser.dart
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:contacts_plus_plus/apis/asset_api.dart';
|
||||||
|
import 'package:contacts_plus_plus/auxiliary.dart';
|
||||||
|
import 'package:contacts_plus_plus/client_holder.dart';
|
||||||
|
import 'package:contacts_plus_plus/models/asset/record.dart';
|
||||||
|
import 'package:contacts_plus_plus/models/inventory/neos_path.dart';
|
||||||
|
import 'package:contacts_plus_plus/widgets/default_error_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class InventoryBrowser extends StatefulWidget {
|
||||||
|
const InventoryBrowser({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _InventoryBrowserState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InventoryBrowserState extends State<InventoryBrowser> with AutomaticKeepAliveClientMixin {
|
||||||
|
static const Duration _refreshLimit = Duration(seconds: 60);
|
||||||
|
Timer? _refreshLimiter;
|
||||||
|
Future<List<Record>>? _inventoryFuture;
|
||||||
|
final NeosPath _inventoryRoot = const NeosPath(name: "Inventory", parent: null, children: [], record: null);
|
||||||
|
late NeosPath _currentPath = _inventoryRoot;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_inventoryFuture = _currentPathFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Record>> _currentPathFuture() => AssetApi.getRecordsAt(
|
||||||
|
ClientHolder.of(context).apiClient,
|
||||||
|
path: _currentPath.absolute,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
if (_refreshLimiter?.isActive ?? false) return;
|
||||||
|
try {
|
||||||
|
final records = await _currentPathFuture();
|
||||||
|
setState(() {
|
||||||
|
_inventoryFuture = Future.value(records);
|
||||||
|
});
|
||||||
|
_refreshLimiter = Timer(_refreshLimit, () {});
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Refresh failed: $e")));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _inventoryFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
final records = snapshot.data as List<Record>;
|
||||||
|
records.sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
final paths = records.where((element) => element.recordType == RecordType.link
|
||||||
|
|| element.recordType == RecordType.directory).toList();
|
||||||
|
final objects = records.where((element) =>
|
||||||
|
element.recordType != RecordType.link && element.recordType != RecordType.directory).toList();
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 0, 0, 16),
|
||||||
|
child: Text(
|
||||||
|
"${_currentPath.absolute}:",
|
||||||
|
style: Theme.of(context).textTheme.labelLarge?.copyWith(color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: GridView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: paths.length,
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 256,
|
||||||
|
childAspectRatio: 4,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
mainAxisSpacing: 8
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final record = paths[index];
|
||||||
|
return PathInventoryTile(record: record);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8,),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: GridView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: objects.length,
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 256,
|
||||||
|
childAspectRatio: 1,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final record = objects[index];
|
||||||
|
return ObjectInventoryTile(record: record);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace));
|
||||||
|
return DefaultErrorWidget(
|
||||||
|
message: snapshot.error.toString(),
|
||||||
|
onRetry: () async {
|
||||||
|
setState(() {
|
||||||
|
_inventoryFuture = null;
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
_inventoryFuture = _currentPathFuture();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
LinearProgressIndicator(),
|
||||||
|
Spacer(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjectInventoryTile extends StatelessWidget {
|
||||||
|
ObjectInventoryTile({required this.record, super.key});
|
||||||
|
|
||||||
|
final Record record;
|
||||||
|
final DateFormat _dateFormat = DateFormat.yMd();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return OutlinedButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16)),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: Aux.neosDbToHttp(record.thumbnailUri),
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||||
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
record.formattedName == null
|
||||||
|
? Text(record.name, maxLines: 2, overflow: TextOverflow.ellipsis)
|
||||||
|
: RichText(text: record.formattedName!, maxLines: 2, overflow: TextOverflow.ellipsis,),
|
||||||
|
if (record.creationTime != null) Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.access_time, size: 12, color: Colors.white54,),
|
||||||
|
const SizedBox(width: 4,),
|
||||||
|
Text(_dateFormat.format(record.creationTime!), style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.white54),),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PathInventoryTile extends StatelessWidget {
|
||||||
|
const PathInventoryTile({required this.record, super.key});
|
||||||
|
|
||||||
|
final Record record;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return OutlinedButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16)),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
alignment: Alignment.centerLeft
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
icon: record.recordType == RecordType.directory ? const Icon(Icons.folder) : const Icon(Icons.link),
|
||||||
|
label: record.formattedName == null
|
||||||
|
? Text(record.name, maxLines: 3, overflow: TextOverflow.ellipsis)
|
||||||
|
: RichText(text: record.formattedName!, maxLines: 3, overflow: TextOverflow.ellipsis),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue