Compare commits
15 Commits
b9ad6d4fd0
...
2.0.0+4
| Author | SHA1 | Date | |
|---|---|---|---|
| fa73a28324 | |||
| d945b103ca | |||
| 8bc0da5188 | |||
| 2e68d227a0 | |||
| b8245b00b6 | |||
| 462e818078 | |||
| e4582b7d25 | |||
| 00eef6e45a | |||
| 9498d428cd | |||
| 654a71e852 | |||
| 455ffcac19 | |||
| 9c8dad0176 | |||
| 2c6b1feca6 | |||
| af044a86bc | |||
| 4884d04a51 |
13
.roadsignrc
Normal file
13
.roadsignrc
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"sync": {
|
||||||
|
"region": "solian-next",
|
||||||
|
"configPath": "roadsign.toml"
|
||||||
|
},
|
||||||
|
"deployments": [
|
||||||
|
{
|
||||||
|
"region": "solian-next",
|
||||||
|
"site": "solian-next-web",
|
||||||
|
"path": "build/web"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -9,6 +9,13 @@
|
|||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- "**/*.g.dart"
|
||||||
|
- "**/*.freezed.dart"
|
||||||
|
errors:
|
||||||
|
invalid_annotation_target: ignore # Due to freezed + json_serializable issue, ref https://github.com/rrousselGit/freezed/issues/488#issuecomment-894358980
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"screenAccountPublisherEdit": "Edit Publisher",
|
"screenAccountPublisherEdit": "Edit Publisher",
|
||||||
"screenAccountProfileEdit": "Edit Profile",
|
"screenAccountProfileEdit": "Edit Profile",
|
||||||
"screenSettings": "Settings",
|
"screenSettings": "Settings",
|
||||||
|
"screenAlbum": "Album",
|
||||||
|
"screenChat": "Chat",
|
||||||
"dialogOkay": "Okay",
|
"dialogOkay": "Okay",
|
||||||
"dialogCancel": "Cancel",
|
"dialogCancel": "Cancel",
|
||||||
"dialogConfirm": "Confirm",
|
"dialogConfirm": "Confirm",
|
||||||
@@ -125,5 +127,11 @@
|
|||||||
"settingsNetworkServerResetDescription": "Reset to the official server address of Solar Network.",
|
"settingsNetworkServerResetDescription": "Reset to the official server address of Solar Network.",
|
||||||
"settingsNetworkServerPreset": "Present HyperNet Server",
|
"settingsNetworkServerPreset": "Present HyperNet Server",
|
||||||
"settingsNetworkServerPresetDescription": "You can choose one of our preset HyperNet server addresses from the list on the right.",
|
"settingsNetworkServerPresetDescription": "You can choose one of our preset HyperNet server addresses from the list on the right.",
|
||||||
"settingsNetworkServerSaved": "Server address saved."
|
"settingsNetworkServerSaved": "Server address saved.",
|
||||||
|
"sensitiveContent": "Sensitive Content",
|
||||||
|
"sensitiveContentCollapsed": "Sensitive content has been collapsed.",
|
||||||
|
"sensitiveContentDescription": "This content has been marked as sensitive, and may not be suitable for all viewers.",
|
||||||
|
"sensitiveContentReveal": "Reveal",
|
||||||
|
"serverConnecting": "Connecting to server...",
|
||||||
|
"serverDisconnected": "Lost connection from server"
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
"screenAccountPublisherEdit": "编辑发布者",
|
"screenAccountPublisherEdit": "编辑发布者",
|
||||||
"screenAccountProfileEdit": "编辑资料",
|
"screenAccountProfileEdit": "编辑资料",
|
||||||
"screenSettings": "设置",
|
"screenSettings": "设置",
|
||||||
|
"screenAlbum": "相册",
|
||||||
|
"screenChat": "聊天",
|
||||||
"dialogOkay": "好的",
|
"dialogOkay": "好的",
|
||||||
"dialogCancel": "取消",
|
"dialogCancel": "取消",
|
||||||
"dialogConfirm": "确认",
|
"dialogConfirm": "确认",
|
||||||
@@ -125,5 +127,11 @@
|
|||||||
"settingsNetworkServerResetDescription": "重设为 Solar Network 的服务器地址。",
|
"settingsNetworkServerResetDescription": "重设为 Solar Network 的服务器地址。",
|
||||||
"settingsNetworkServerPreset": "预设的 HyperNet 服务器",
|
"settingsNetworkServerPreset": "预设的 HyperNet 服务器",
|
||||||
"settingsNetworkServerPresetDescription": "你可以在旁边的列表中选择我们提供的预设 HyperNet 服务器地址。",
|
"settingsNetworkServerPresetDescription": "你可以在旁边的列表中选择我们提供的预设 HyperNet 服务器地址。",
|
||||||
"settingsNetworkServerSaved": "服务器地址已保存。"
|
"settingsNetworkServerSaved": "服务器地址已保存。",
|
||||||
|
"sensitiveContent": "敏感内容",
|
||||||
|
"sensitiveContentCollapsed": "敏感内容已折叠。",
|
||||||
|
"sensitiveContentDescription": "此内容已被标记,可能不适合所有人查看。",
|
||||||
|
"sensitiveContentReveal": "显示内容",
|
||||||
|
"serverConnecting": "正在连接服务器…",
|
||||||
|
"serverDisconnected": "已与服务器断开连接"
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ PODS:
|
|||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_native_splash (0.0.1):
|
- flutter_native_splash (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_secure_storage (3.3.1):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -119,7 +119,7 @@ SPEC CHECKSUMS:
|
|||||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||||
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
||||||
|
|||||||
@@ -161,7 +161,6 @@
|
|||||||
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */,
|
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */,
|
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Pods;
|
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -474,6 +473,7 @@
|
|||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -657,6 +657,7 @@
|
|||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -680,6 +681,7 @@
|
|||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Surface</string>
|
<string>Solian</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>surface</string>
|
<string>Solian</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
|
import 'package:surface/providers/navigation.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/theme.dart';
|
import 'package:surface/providers/theme.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/providers/websocket.dart';
|
||||||
import 'package:surface/router.dart';
|
import 'package:surface/router.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -34,13 +36,19 @@ class SolianApp extends StatelessWidget {
|
|||||||
supportedLocales: [Locale('en', 'US'), Locale('zh', 'CN')],
|
supportedLocales: [Locale('en', 'US'), Locale('zh', 'CN')],
|
||||||
fallbackLocale: Locale('en', 'US'),
|
fallbackLocale: Locale('en', 'US'),
|
||||||
useFallbackTranslations: true,
|
useFallbackTranslations: true,
|
||||||
|
useOnlyLangCode: true,
|
||||||
assetLoader: JsonAssetLoader(),
|
assetLoader: JsonAssetLoader(),
|
||||||
child: MultiProvider(
|
child: MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
// Display layer
|
||||||
|
ChangeNotifierProvider(create: (_) => ThemeProvider()),
|
||||||
|
ChangeNotifierProvider(create: (ctx) => NavigationProvider()),
|
||||||
|
|
||||||
|
// Data layer
|
||||||
Provider(create: (_) => SnNetworkProvider()),
|
Provider(create: (_) => SnNetworkProvider()),
|
||||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (_) => ThemeProvider()),
|
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
|
||||||
],
|
],
|
||||||
child: AppMainContent(),
|
child: AppMainContent(),
|
||||||
),
|
),
|
||||||
@@ -59,7 +67,8 @@ class AppMainContent extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
context.read<UserProvider>();
|
context.read<NavigationProvider>();
|
||||||
|
context.read<WebSocketProvider>();
|
||||||
|
|
||||||
final th = context.watch<ThemeProvider>();
|
final th = context.watch<ThemeProvider>();
|
||||||
|
|
||||||
|
|||||||
112
lib/providers/navigation.dart
Normal file
112
lib/providers/navigation.dart
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class AppNavDestination {
|
||||||
|
final String label;
|
||||||
|
final String screen;
|
||||||
|
final Widget icon;
|
||||||
|
final bool isPinned;
|
||||||
|
|
||||||
|
const AppNavDestination({
|
||||||
|
required this.label,
|
||||||
|
required this.screen,
|
||||||
|
required this.icon,
|
||||||
|
this.isPinned = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NavigationProvider extends ChangeNotifier {
|
||||||
|
int? _currentIndex;
|
||||||
|
|
||||||
|
int? get currentIndex => _currentIndex;
|
||||||
|
|
||||||
|
static const List<AppNavDestination> kAllDestination = [
|
||||||
|
AppNavDestination(
|
||||||
|
icon: Icon(Symbols.home, weight: 400, opticalSize: 20),
|
||||||
|
screen: 'home',
|
||||||
|
label: 'screenHome',
|
||||||
|
),
|
||||||
|
AppNavDestination(
|
||||||
|
icon: Icon(Symbols.explore, weight: 400, opticalSize: 20),
|
||||||
|
screen: 'explore',
|
||||||
|
label: 'screenExplore',
|
||||||
|
),
|
||||||
|
AppNavDestination(
|
||||||
|
icon: Icon(Symbols.account_circle, weight: 400, opticalSize: 20),
|
||||||
|
screen: 'account',
|
||||||
|
label: 'screenAccount',
|
||||||
|
),
|
||||||
|
AppNavDestination(
|
||||||
|
icon: Icon(Symbols.album, weight: 400, opticalSize: 20),
|
||||||
|
screen: 'album',
|
||||||
|
label: 'screenAlbum',
|
||||||
|
),
|
||||||
|
AppNavDestination(
|
||||||
|
icon: Icon(Symbols.chat, weight: 400, opticalSize: 20),
|
||||||
|
screen: 'chat',
|
||||||
|
label: 'screenChat',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
static const List<String> kDefaultPinnedDestination = [
|
||||||
|
'home',
|
||||||
|
'explore',
|
||||||
|
'account'
|
||||||
|
];
|
||||||
|
|
||||||
|
List<AppNavDestination> destinations = [];
|
||||||
|
|
||||||
|
int get pinnedDestinationCount =>
|
||||||
|
destinations.where((ele) => ele.isPinned).length;
|
||||||
|
|
||||||
|
NavigationProvider() {
|
||||||
|
buildDestinations(kDefaultPinnedDestination);
|
||||||
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
|
final pinned = prefs.getStringList("app_pinned_navigation");
|
||||||
|
if (pinned != null) buildDestinations(pinned);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildDestinations(List<String> pinned) {
|
||||||
|
destinations = kAllDestination
|
||||||
|
.map(
|
||||||
|
(ele) => AppNavDestination(
|
||||||
|
label: ele.label,
|
||||||
|
screen: ele.screen,
|
||||||
|
icon: ele.icon,
|
||||||
|
isPinned: pinned.contains(ele.screen),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getIndexInRange(int min, int max) {
|
||||||
|
return math.max(min, math.min(_currentIndex ?? 0, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isIndexInRange(int min, int max) {
|
||||||
|
return _currentIndex != null &&
|
||||||
|
_currentIndex! >= min &&
|
||||||
|
_currentIndex! < max;
|
||||||
|
}
|
||||||
|
|
||||||
|
void autoDetectIndex(GoRouter? state) {
|
||||||
|
if (state == null) return;
|
||||||
|
final idx = destinations.indexWhere(
|
||||||
|
(ele) =>
|
||||||
|
ele.screen ==
|
||||||
|
state.routerDelegate.currentConfiguration.last.route.name,
|
||||||
|
);
|
||||||
|
_currentIndex = idx == -1 ? null : idx;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setIndex(int idx) {
|
||||||
|
_currentIndex = idx;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,25 @@ class SnNetworkProvider {
|
|||||||
RequestOptions options,
|
RequestOptions options,
|
||||||
RequestInterceptorHandler handler,
|
RequestInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
|
final atk = await getFreshAtk();
|
||||||
|
if (atk != null) {
|
||||||
|
options.headers['Authorization'] = 'Bearer $atk';
|
||||||
|
}
|
||||||
|
return handler.next(options);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
client = addClientAdapter(client);
|
||||||
|
|
||||||
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
|
_prefs = prefs;
|
||||||
|
client.options.baseUrl =
|
||||||
|
_prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getFreshAtk() async {
|
||||||
try {
|
try {
|
||||||
var atk = await _storage.read(key: kAtkStoreKey);
|
var atk = await _storage.read(key: kAtkStoreKey);
|
||||||
if (atk != null) {
|
if (atk != null) {
|
||||||
@@ -52,8 +71,7 @@ class SnNetworkProvider {
|
|||||||
throw Exception('invalid format of access token');
|
throw Exception('invalid format of access token');
|
||||||
}
|
}
|
||||||
|
|
||||||
var rawPayload =
|
var rawPayload = atkParts[1].replaceAll('-', '+').replaceAll('_', '/');
|
||||||
atkParts[1].replaceAll('-', '+').replaceAll('_', '/');
|
|
||||||
switch (rawPayload.length % 4) {
|
switch (rawPayload.length % 4) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
@@ -76,27 +94,16 @@ class SnNetworkProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (atk != null) {
|
if (atk != null) {
|
||||||
options.headers['Authorization'] = 'Bearer $atk';
|
return atk;
|
||||||
} else {
|
} else {
|
||||||
log('Access token refresh failed...');
|
log('Access token refresh failed...');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log('Failed to authenticate user: $err');
|
log('Failed to authenticate user: $err');
|
||||||
} finally {
|
|
||||||
handler.next(options);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
client = addClientAdapter(client);
|
return null;
|
||||||
|
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
|
||||||
_prefs = prefs;
|
|
||||||
client.options.baseUrl =
|
|
||||||
_prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getAttachmentUrl(String ky) {
|
String getAttachmentUrl(String ky) {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ class UserProvider extends ChangeNotifier {
|
|||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final FlutterSecureStorage _storage = FlutterSecureStorage();
|
late final FlutterSecureStorage _storage = FlutterSecureStorage();
|
||||||
|
|
||||||
|
Future<String?> get atk => _storage.read(key: kAtkStoreKey);
|
||||||
|
|
||||||
UserProvider(BuildContext context) {
|
UserProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
|||||||
110
lib/providers/websocket.dart
Normal file
110
lib/providers/websocket.dart
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/types/websocket.dart';
|
||||||
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
|
||||||
|
class WebSocketProvider extends ChangeNotifier {
|
||||||
|
bool isBusy = false;
|
||||||
|
bool isConnected = false;
|
||||||
|
|
||||||
|
WebSocketChannel? conn;
|
||||||
|
|
||||||
|
late final SnNetworkProvider _sn;
|
||||||
|
late final UserProvider _ua;
|
||||||
|
|
||||||
|
StreamController<WebSocketPackage> stream = StreamController.broadcast();
|
||||||
|
|
||||||
|
WebSocketProvider(BuildContext context) {
|
||||||
|
_sn = context.read<SnNetworkProvider>();
|
||||||
|
_ua = context.read<UserProvider>();
|
||||||
|
|
||||||
|
// Wait for the userinfo provide initialize authorization status
|
||||||
|
Future.delayed(const Duration(milliseconds: 250), () async {
|
||||||
|
if (_ua.isAuthorized) {
|
||||||
|
log('[WebSocket] Connecting to the server...');
|
||||||
|
await connect();
|
||||||
|
} else {
|
||||||
|
log('[WebSocket] Unable connect to the server, unauthorized.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> connect({noRetry = false}) async {
|
||||||
|
if (!_ua.isAuthorized) return;
|
||||||
|
if (isConnected) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
final atk = await _sn.getFreshAtk();
|
||||||
|
final uri = Uri.parse(
|
||||||
|
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
||||||
|
);
|
||||||
|
|
||||||
|
isBusy = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn = WebSocketChannel.connect(uri);
|
||||||
|
await conn!.ready;
|
||||||
|
log('[WebSocket] Connected to server!');
|
||||||
|
isConnected = true;
|
||||||
|
} catch (err) {
|
||||||
|
if (err is WebSocketChannelException) {
|
||||||
|
log('Failed to connect to websocket: ${(err.inner as dynamic).message}');
|
||||||
|
} else {
|
||||||
|
log('Failed to connect to websocket: $err');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noRetry) {
|
||||||
|
log('Retry connecting to websocket in 3 seconds...');
|
||||||
|
return Future.delayed(
|
||||||
|
const Duration(seconds: 3),
|
||||||
|
() => connect(noRetry: true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isBusy = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void disconnect() {
|
||||||
|
if (conn != null) {
|
||||||
|
conn!.sink.close();
|
||||||
|
}
|
||||||
|
isConnected = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void listen() {
|
||||||
|
conn?.stream.listen(
|
||||||
|
(event) {
|
||||||
|
final packet = WebSocketPackage.fromJson(jsonDecode(event));
|
||||||
|
log('Websocket incoming message: ${packet.method} ${packet.message}');
|
||||||
|
stream.sink.add(packet);
|
||||||
|
// TODO handle notification
|
||||||
|
// if (packet.method == 'notifications.new') {
|
||||||
|
// final NotificationProvider nty = Get.find();
|
||||||
|
// nty.notifications.add(Notification.fromJson(packet.payload!));
|
||||||
|
// nty.notificationUnread.value++;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
isConnected = false;
|
||||||
|
notifyListeners();
|
||||||
|
Future.delayed(const Duration(seconds: 1), () => connect());
|
||||||
|
},
|
||||||
|
onError: (err) {
|
||||||
|
isConnected = false;
|
||||||
|
notifyListeners();
|
||||||
|
Future.delayed(const Duration(seconds: 11), () => connect());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ import 'package:surface/screens/account/profile_edit.dart';
|
|||||||
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
import 'package:surface/screens/account/publishers/publisher_edit.dart';
|
||||||
import 'package:surface/screens/account/publishers/publisher_new.dart';
|
import 'package:surface/screens/account/publishers/publisher_new.dart';
|
||||||
import 'package:surface/screens/account/publishers/publishers.dart';
|
import 'package:surface/screens/account/publishers/publishers.dart';
|
||||||
|
import 'package:surface/screens/album.dart';
|
||||||
import 'package:surface/screens/auth/login.dart';
|
import 'package:surface/screens/auth/login.dart';
|
||||||
import 'package:surface/screens/auth/register.dart';
|
import 'package:surface/screens/auth/register.dart';
|
||||||
|
import 'package:surface/screens/chat.dart';
|
||||||
import 'package:surface/screens/explore.dart';
|
import 'package:surface/screens/explore.dart';
|
||||||
import 'package:surface/screens/home.dart';
|
import 'package:surface/screens/home.dart';
|
||||||
import 'package:surface/screens/post/post_detail.dart';
|
import 'package:surface/screens/post/post_detail.dart';
|
||||||
@@ -14,12 +16,12 @@ import 'package:surface/screens/settings.dart';
|
|||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
final appRouter = GoRouter(
|
final _appRoutes = [
|
||||||
routes: [
|
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (context, state, child) => AppScaffold(
|
builder: (context, state, child) => AppScaffold(
|
||||||
body: child,
|
body: child,
|
||||||
showBottomNavigation: true,
|
showBottomNavigation: true,
|
||||||
|
showAppBar: false,
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@@ -37,12 +39,20 @@ final appRouter = GoRouter(
|
|||||||
name: 'account',
|
name: 'account',
|
||||||
builder: (context, state) => const AccountScreen(),
|
builder: (context, state) => const AccountScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat',
|
||||||
|
name: 'chat',
|
||||||
|
builder: (context, state) => const ChatScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/album',
|
||||||
|
name: 'album',
|
||||||
|
builder: (context, state) => const AlbumScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (context, state, child) => AppScaffold(
|
builder: (context, state, child) => child,
|
||||||
body: child,
|
|
||||||
),
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/post/write/:mode',
|
path: '/post/write/:mode',
|
||||||
@@ -71,10 +81,7 @@ final appRouter = GoRouter(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (context, state, child) => AppScaffold(
|
builder: (context, state, child) => AppScaffold(body: child),
|
||||||
body: child,
|
|
||||||
autoImplyAppBar: true,
|
|
||||||
),
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/auth/login',
|
path: '/auth/login',
|
||||||
@@ -111,10 +118,7 @@ final appRouter = GoRouter(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (context, state, child) => AppScaffold(
|
builder: (context, state, child) => AppScaffold(body: child),
|
||||||
body: child,
|
|
||||||
autoImplyAppBar: true,
|
|
||||||
),
|
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
@@ -123,5 +127,13 @@ final appRouter = GoRouter(
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
final appRouter = GoRouter(
|
||||||
|
routes: [
|
||||||
|
ShellRoute(
|
||||||
|
builder: (context, state, child) => AppRootScaffold(body: child),
|
||||||
|
routes: _appRoutes,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
|
|
||||||
class AccountScreen extends StatelessWidget {
|
class AccountScreen extends StatelessWidget {
|
||||||
const AccountScreen({super.key});
|
const AccountScreen({super.key});
|
||||||
@@ -17,7 +16,7 @@ class AccountScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ua = context.watch<UserProvider>();
|
final ua = context.watch<UserProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("screenAccount").tr(),
|
title: Text("screenAccount").tr(),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import 'package:surface/types/post.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
class AccountPublisherEditScreen extends StatefulWidget {
|
class AccountPublisherEditScreen extends StatefulWidget {
|
||||||
@@ -189,7 +188,7 @@ class _AccountPublisherEditScreenState
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
|
|
||||||
class AccountPublisherNewScreen extends StatefulWidget {
|
class AccountPublisherNewScreen extends StatefulWidget {
|
||||||
const AccountPublisherNewScreen({super.key});
|
const AccountPublisherNewScreen({super.key});
|
||||||
@@ -23,7 +22,7 @@ class _AccountPublisherNewScreenState extends State<AccountPublisherNewScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@@ -11,7 +10,6 @@ import 'package:surface/types/post.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
|
|
||||||
class PublisherScreen extends StatefulWidget {
|
class PublisherScreen extends StatefulWidget {
|
||||||
const PublisherScreen({super.key});
|
const PublisherScreen({super.key});
|
||||||
@@ -55,7 +53,7 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|||||||
10
lib/screens/album.dart
Normal file
10
lib/screens/album.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AlbumScreen extends StatelessWidget {
|
||||||
|
const AlbumScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -271,13 +271,14 @@ class _LoginPickerScreenState extends State<_LoginPickerScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Request one-time-password code
|
// Request one-time-password code
|
||||||
sn.client.post('/cgi/id/auth/factors/$_factorPicked');
|
await sn.client.post('/cgi/id/auth/factors/$_factorPicked');
|
||||||
widget.onPickFactor(
|
widget.onPickFactor(
|
||||||
widget.factors!.where((x) => x.id == _factorPicked).first,
|
widget.factors!.where((x) => x.id == _factorPicked).first,
|
||||||
);
|
);
|
||||||
widget.onNext();
|
widget.onNext();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
context.showErrorDialog(err);
|
// ignore: use_build_context_synchronously
|
||||||
|
if (context.mounted) context.showErrorDialog(err);
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
|
|||||||
10
lib/screens/chat.dart
Normal file
10
lib/screens/chat.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ChatScreen extends StatelessWidget {
|
||||||
|
const ChatScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,10 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
key: _fabKey,
|
key: _fabKey,
|
||||||
@@ -173,7 +173,10 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
onFetchData: _fetchPosts,
|
onFetchData: _fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
child: PostItem(data: _posts[idx]),
|
child: PostItem(data: _posts[idx]),
|
||||||
|
).center(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postDetail',
|
'postDetail',
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
@@ -14,7 +13,7 @@ class HomeScreen extends StatefulWidget {
|
|||||||
class _HomeScreenState extends State<HomeScreen> {
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("screenHome").tr(),
|
title: Text("screenHome").tr(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
import 'package:surface/widgets/post/post_comment_list.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_mini_editor.dart';
|
import 'package:surface/widgets/post/post_mini_editor.dart';
|
||||||
@@ -47,13 +47,11 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
resp.data['body']['attachments']?.cast<String>() ?? [],
|
resp.data['body']['attachments']?.cast<String>() ?? [],
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
|
||||||
_data = SnPost.fromJson(resp.data).copyWith(
|
_data = SnPost.fromJson(resp.data).copyWith(
|
||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -74,9 +72,10 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final ua = context.watch<UserProvider>();
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: BackButton(
|
leading: BackButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -87,13 +86,19 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
flexibleSpace: Column(
|
flexibleSpace: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
if (_data?.body['title'] != null)
|
||||||
Text(_data?.body['title'] ?? 'postNoun'.tr())
|
Text(_data?.body['title'] ?? 'postNoun'.tr())
|
||||||
.textStyle(Theme.of(context).textTheme.titleLarge!)
|
.textStyle(Theme.of(context).textTheme.titleLarge!)
|
||||||
.textColor(Colors.white),
|
.textColor(Colors.white),
|
||||||
Text('postDetail')
|
if (_data?.body['title'] != null)
|
||||||
.tr()
|
Text('postDetail'.tr())
|
||||||
.textColor(Colors.white.withAlpha((255 * 0.9).round())),
|
.textColor(Colors.white.withAlpha((255 * 0.9).round()))
|
||||||
|
else
|
||||||
|
Text('postDetail'.tr())
|
||||||
|
.textStyle(Theme.of(context).textTheme.titleLarge!)
|
||||||
|
.textColor(Colors.white),
|
||||||
],
|
],
|
||||||
).padding(top: math.max(MediaQuery.of(context).padding.top, 8)),
|
).padding(top: math.max(MediaQuery.of(context).padding.top, 8)),
|
||||||
),
|
),
|
||||||
@@ -104,7 +109,13 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
),
|
),
|
||||||
if (_data != null)
|
if (_data != null)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: PostItem(data: _data!, showComments: false),
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: PostItem(
|
||||||
|
data: _data!,
|
||||||
|
showComments: false,
|
||||||
|
),
|
||||||
|
).center(),
|
||||||
),
|
),
|
||||||
const SliverToBoxAdapter(child: Divider(height: 1)),
|
const SliverToBoxAdapter(child: Divider(height: 1)),
|
||||||
if (_data != null)
|
if (_data != null)
|
||||||
@@ -120,7 +131,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 20, vertical: 12),
|
).padding(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
if (_data != null)
|
if (_data != null && ua.isAuthorized)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 240,
|
height: 240,
|
||||||
@@ -151,6 +162,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
PostCommentSliverList(
|
PostCommentSliverList(
|
||||||
key: _childListKey,
|
key: _childListKey,
|
||||||
parentPostId: _data!.id,
|
parentPostId: _data!.id,
|
||||||
|
maxWidth: 640,
|
||||||
),
|
),
|
||||||
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||||
import 'package:surface/widgets/post/post_meta_editor.dart';
|
import 'package:surface/widgets/post/post_meta_editor.dart';
|
||||||
@@ -111,7 +110,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: _writeController,
|
listenable: _writeController,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: BackButton(
|
leading: BackButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/theme.dart';
|
import 'package:surface/providers/theme.dart';
|
||||||
import 'package:surface/theme.dart';
|
import 'package:surface/theme.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
|
||||||
|
|
||||||
class SettingsScreen extends StatefulWidget {
|
class SettingsScreen extends StatefulWidget {
|
||||||
const SettingsScreen({super.key});
|
const SettingsScreen({super.key});
|
||||||
@@ -58,7 +57,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return Scaffold(
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ class SnPost with _$SnPost {
|
|||||||
required String? aliasPrefix,
|
required String? aliasPrefix,
|
||||||
required List<dynamic> tags,
|
required List<dynamic> tags,
|
||||||
required List<dynamic> categories,
|
required List<dynamic> categories,
|
||||||
required dynamic replies,
|
required List<SnPost>? replies,
|
||||||
required dynamic replyId,
|
required int? replyId,
|
||||||
required dynamic repostId,
|
required int? repostId,
|
||||||
required dynamic replyTo,
|
required SnPost? replyTo,
|
||||||
required dynamic repostTo,
|
required SnPost? repostTo,
|
||||||
required dynamic visibleUsersList,
|
required List<int>? visibleUsersList,
|
||||||
required dynamic invisibleUsersList,
|
required List<int>? invisibleUsersList,
|
||||||
required int visibility,
|
required int visibility,
|
||||||
required DateTime? editedAt,
|
required DateTime? editedAt,
|
||||||
required DateTime? pinnedAt,
|
required DateTime? pinnedAt,
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ mixin _$SnPost {
|
|||||||
String? get aliasPrefix => throw _privateConstructorUsedError;
|
String? get aliasPrefix => throw _privateConstructorUsedError;
|
||||||
List<dynamic> get tags => throw _privateConstructorUsedError;
|
List<dynamic> get tags => throw _privateConstructorUsedError;
|
||||||
List<dynamic> get categories => throw _privateConstructorUsedError;
|
List<dynamic> get categories => throw _privateConstructorUsedError;
|
||||||
dynamic get replies => throw _privateConstructorUsedError;
|
List<SnPost>? get replies => throw _privateConstructorUsedError;
|
||||||
dynamic get replyId => throw _privateConstructorUsedError;
|
int? get replyId => throw _privateConstructorUsedError;
|
||||||
dynamic get repostId => throw _privateConstructorUsedError;
|
int? get repostId => throw _privateConstructorUsedError;
|
||||||
dynamic get replyTo => throw _privateConstructorUsedError;
|
SnPost? get replyTo => throw _privateConstructorUsedError;
|
||||||
dynamic get repostTo => throw _privateConstructorUsedError;
|
SnPost? get repostTo => throw _privateConstructorUsedError;
|
||||||
dynamic get visibleUsersList => throw _privateConstructorUsedError;
|
List<int>? get visibleUsersList => throw _privateConstructorUsedError;
|
||||||
dynamic get invisibleUsersList => throw _privateConstructorUsedError;
|
List<int>? get invisibleUsersList => throw _privateConstructorUsedError;
|
||||||
int get visibility => throw _privateConstructorUsedError;
|
int get visibility => throw _privateConstructorUsedError;
|
||||||
DateTime? get editedAt => throw _privateConstructorUsedError;
|
DateTime? get editedAt => throw _privateConstructorUsedError;
|
||||||
DateTime? get pinnedAt => throw _privateConstructorUsedError;
|
DateTime? get pinnedAt => throw _privateConstructorUsedError;
|
||||||
@@ -78,13 +78,13 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
String? aliasPrefix,
|
String? aliasPrefix,
|
||||||
List<dynamic> tags,
|
List<dynamic> tags,
|
||||||
List<dynamic> categories,
|
List<dynamic> categories,
|
||||||
dynamic replies,
|
List<SnPost>? replies,
|
||||||
dynamic replyId,
|
int? replyId,
|
||||||
dynamic repostId,
|
int? repostId,
|
||||||
dynamic replyTo,
|
SnPost? replyTo,
|
||||||
dynamic repostTo,
|
SnPost? repostTo,
|
||||||
dynamic visibleUsersList,
|
List<int>? visibleUsersList,
|
||||||
dynamic invisibleUsersList,
|
List<int>? invisibleUsersList,
|
||||||
int visibility,
|
int visibility,
|
||||||
DateTime? editedAt,
|
DateTime? editedAt,
|
||||||
DateTime? pinnedAt,
|
DateTime? pinnedAt,
|
||||||
@@ -99,6 +99,8 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
SnMetric metric,
|
SnMetric metric,
|
||||||
SnPostPreload? preload});
|
SnPostPreload? preload});
|
||||||
|
|
||||||
|
$SnPostCopyWith<$Res>? get replyTo;
|
||||||
|
$SnPostCopyWith<$Res>? get repostTo;
|
||||||
$SnPublisherCopyWith<$Res> get publisher;
|
$SnPublisherCopyWith<$Res> get publisher;
|
||||||
$SnMetricCopyWith<$Res> get metric;
|
$SnMetricCopyWith<$Res> get metric;
|
||||||
$SnPostPreloadCopyWith<$Res>? get preload;
|
$SnPostPreloadCopyWith<$Res>? get preload;
|
||||||
@@ -199,31 +201,31 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
replies: freezed == replies
|
replies: freezed == replies
|
||||||
? _value.replies
|
? _value.replies
|
||||||
: replies // ignore: cast_nullable_to_non_nullable
|
: replies // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as List<SnPost>?,
|
||||||
replyId: freezed == replyId
|
replyId: freezed == replyId
|
||||||
? _value.replyId
|
? _value.replyId
|
||||||
: replyId // ignore: cast_nullable_to_non_nullable
|
: replyId // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as int?,
|
||||||
repostId: freezed == repostId
|
repostId: freezed == repostId
|
||||||
? _value.repostId
|
? _value.repostId
|
||||||
: repostId // ignore: cast_nullable_to_non_nullable
|
: repostId // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as int?,
|
||||||
replyTo: freezed == replyTo
|
replyTo: freezed == replyTo
|
||||||
? _value.replyTo
|
? _value.replyTo
|
||||||
: replyTo // ignore: cast_nullable_to_non_nullable
|
: replyTo // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as SnPost?,
|
||||||
repostTo: freezed == repostTo
|
repostTo: freezed == repostTo
|
||||||
? _value.repostTo
|
? _value.repostTo
|
||||||
: repostTo // ignore: cast_nullable_to_non_nullable
|
: repostTo // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as SnPost?,
|
||||||
visibleUsersList: freezed == visibleUsersList
|
visibleUsersList: freezed == visibleUsersList
|
||||||
? _value.visibleUsersList
|
? _value.visibleUsersList
|
||||||
: visibleUsersList // ignore: cast_nullable_to_non_nullable
|
: visibleUsersList // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as List<int>?,
|
||||||
invisibleUsersList: freezed == invisibleUsersList
|
invisibleUsersList: freezed == invisibleUsersList
|
||||||
? _value.invisibleUsersList
|
? _value.invisibleUsersList
|
||||||
: invisibleUsersList // ignore: cast_nullable_to_non_nullable
|
: invisibleUsersList // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as List<int>?,
|
||||||
visibility: null == visibility
|
visibility: null == visibility
|
||||||
? _value.visibility
|
? _value.visibility
|
||||||
: visibility // ignore: cast_nullable_to_non_nullable
|
: visibility // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -279,6 +281,34 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPost
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPostCopyWith<$Res>? get replyTo {
|
||||||
|
if (_value.replyTo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPostCopyWith<$Res>(_value.replyTo!, (value) {
|
||||||
|
return _then(_value.copyWith(replyTo: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPost
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPostCopyWith<$Res>? get repostTo {
|
||||||
|
if (_value.repostTo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPostCopyWith<$Res>(_value.repostTo!, (value) {
|
||||||
|
return _then(_value.copyWith(repostTo: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override
|
@override
|
||||||
@@ -333,13 +363,13 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
String? aliasPrefix,
|
String? aliasPrefix,
|
||||||
List<dynamic> tags,
|
List<dynamic> tags,
|
||||||
List<dynamic> categories,
|
List<dynamic> categories,
|
||||||
dynamic replies,
|
List<SnPost>? replies,
|
||||||
dynamic replyId,
|
int? replyId,
|
||||||
dynamic repostId,
|
int? repostId,
|
||||||
dynamic replyTo,
|
SnPost? replyTo,
|
||||||
dynamic repostTo,
|
SnPost? repostTo,
|
||||||
dynamic visibleUsersList,
|
List<int>? visibleUsersList,
|
||||||
dynamic invisibleUsersList,
|
List<int>? invisibleUsersList,
|
||||||
int visibility,
|
int visibility,
|
||||||
DateTime? editedAt,
|
DateTime? editedAt,
|
||||||
DateTime? pinnedAt,
|
DateTime? pinnedAt,
|
||||||
@@ -354,6 +384,10 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
SnMetric metric,
|
SnMetric metric,
|
||||||
SnPostPreload? preload});
|
SnPostPreload? preload});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnPostCopyWith<$Res>? get replyTo;
|
||||||
|
@override
|
||||||
|
$SnPostCopyWith<$Res>? get repostTo;
|
||||||
@override
|
@override
|
||||||
$SnPublisherCopyWith<$Res> get publisher;
|
$SnPublisherCopyWith<$Res> get publisher;
|
||||||
@override
|
@override
|
||||||
@@ -453,33 +487,33 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
: categories // ignore: cast_nullable_to_non_nullable
|
: categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,
|
as List<dynamic>,
|
||||||
replies: freezed == replies
|
replies: freezed == replies
|
||||||
? _value.replies
|
? _value._replies
|
||||||
: replies // ignore: cast_nullable_to_non_nullable
|
: replies // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as List<SnPost>?,
|
||||||
replyId: freezed == replyId
|
replyId: freezed == replyId
|
||||||
? _value.replyId
|
? _value.replyId
|
||||||
: replyId // ignore: cast_nullable_to_non_nullable
|
: replyId // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as int?,
|
||||||
repostId: freezed == repostId
|
repostId: freezed == repostId
|
||||||
? _value.repostId
|
? _value.repostId
|
||||||
: repostId // ignore: cast_nullable_to_non_nullable
|
: repostId // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as int?,
|
||||||
replyTo: freezed == replyTo
|
replyTo: freezed == replyTo
|
||||||
? _value.replyTo
|
? _value.replyTo
|
||||||
: replyTo // ignore: cast_nullable_to_non_nullable
|
: replyTo // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as SnPost?,
|
||||||
repostTo: freezed == repostTo
|
repostTo: freezed == repostTo
|
||||||
? _value.repostTo
|
? _value.repostTo
|
||||||
: repostTo // ignore: cast_nullable_to_non_nullable
|
: repostTo // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as SnPost?,
|
||||||
visibleUsersList: freezed == visibleUsersList
|
visibleUsersList: freezed == visibleUsersList
|
||||||
? _value.visibleUsersList
|
? _value._visibleUsersList
|
||||||
: visibleUsersList // ignore: cast_nullable_to_non_nullable
|
: visibleUsersList // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as List<int>?,
|
||||||
invisibleUsersList: freezed == invisibleUsersList
|
invisibleUsersList: freezed == invisibleUsersList
|
||||||
? _value.invisibleUsersList
|
? _value._invisibleUsersList
|
||||||
: invisibleUsersList // ignore: cast_nullable_to_non_nullable
|
: invisibleUsersList // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as List<int>?,
|
||||||
visibility: null == visibility
|
visibility: null == visibility
|
||||||
? _value.visibility
|
? _value.visibility
|
||||||
: visibility // ignore: cast_nullable_to_non_nullable
|
: visibility // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -551,13 +585,13 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
required this.aliasPrefix,
|
required this.aliasPrefix,
|
||||||
required final List<dynamic> tags,
|
required final List<dynamic> tags,
|
||||||
required final List<dynamic> categories,
|
required final List<dynamic> categories,
|
||||||
required this.replies,
|
required final List<SnPost>? replies,
|
||||||
required this.replyId,
|
required this.replyId,
|
||||||
required this.repostId,
|
required this.repostId,
|
||||||
required this.replyTo,
|
required this.replyTo,
|
||||||
required this.repostTo,
|
required this.repostTo,
|
||||||
required this.visibleUsersList,
|
required final List<int>? visibleUsersList,
|
||||||
required this.invisibleUsersList,
|
required final List<int>? invisibleUsersList,
|
||||||
required this.visibility,
|
required this.visibility,
|
||||||
required this.editedAt,
|
required this.editedAt,
|
||||||
required this.pinnedAt,
|
required this.pinnedAt,
|
||||||
@@ -574,6 +608,9 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
: _body = body,
|
: _body = body,
|
||||||
_tags = tags,
|
_tags = tags,
|
||||||
_categories = categories,
|
_categories = categories,
|
||||||
|
_replies = replies,
|
||||||
|
_visibleUsersList = visibleUsersList,
|
||||||
|
_invisibleUsersList = invisibleUsersList,
|
||||||
super._();
|
super._();
|
||||||
|
|
||||||
factory _$SnPostImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SnPostImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -619,20 +656,46 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
return EqualUnmodifiableListView(_categories);
|
return EqualUnmodifiableListView(_categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<SnPost>? _replies;
|
||||||
@override
|
@override
|
||||||
final dynamic replies;
|
List<SnPost>? get replies {
|
||||||
|
final value = _replies;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_replies is EqualUnmodifiableListView) return _replies;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final dynamic replyId;
|
final int? replyId;
|
||||||
@override
|
@override
|
||||||
final dynamic repostId;
|
final int? repostId;
|
||||||
@override
|
@override
|
||||||
final dynamic replyTo;
|
final SnPost? replyTo;
|
||||||
@override
|
@override
|
||||||
final dynamic repostTo;
|
final SnPost? repostTo;
|
||||||
|
final List<int>? _visibleUsersList;
|
||||||
@override
|
@override
|
||||||
final dynamic visibleUsersList;
|
List<int>? get visibleUsersList {
|
||||||
|
final value = _visibleUsersList;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_visibleUsersList is EqualUnmodifiableListView)
|
||||||
|
return _visibleUsersList;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<int>? _invisibleUsersList;
|
||||||
@override
|
@override
|
||||||
final dynamic invisibleUsersList;
|
List<int>? get invisibleUsersList {
|
||||||
|
final value = _invisibleUsersList;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_invisibleUsersList is EqualUnmodifiableListView)
|
||||||
|
return _invisibleUsersList;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int visibility;
|
final int visibility;
|
||||||
@override
|
@override
|
||||||
@@ -687,15 +750,17 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._categories, _categories) &&
|
.equals(other._categories, _categories) &&
|
||||||
const DeepCollectionEquality().equals(other.replies, replies) &&
|
const DeepCollectionEquality().equals(other._replies, _replies) &&
|
||||||
const DeepCollectionEquality().equals(other.replyId, replyId) &&
|
(identical(other.replyId, replyId) || other.replyId == replyId) &&
|
||||||
const DeepCollectionEquality().equals(other.repostId, repostId) &&
|
(identical(other.repostId, repostId) ||
|
||||||
const DeepCollectionEquality().equals(other.replyTo, replyTo) &&
|
other.repostId == repostId) &&
|
||||||
const DeepCollectionEquality().equals(other.repostTo, repostTo) &&
|
(identical(other.replyTo, replyTo) || other.replyTo == replyTo) &&
|
||||||
|
(identical(other.repostTo, repostTo) ||
|
||||||
|
other.repostTo == repostTo) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other.visibleUsersList, visibleUsersList) &&
|
.equals(other._visibleUsersList, _visibleUsersList) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other.invisibleUsersList, invisibleUsersList) &&
|
.equals(other._invisibleUsersList, _invisibleUsersList) &&
|
||||||
(identical(other.visibility, visibility) ||
|
(identical(other.visibility, visibility) ||
|
||||||
other.visibility == visibility) &&
|
other.visibility == visibility) &&
|
||||||
(identical(other.editedAt, editedAt) ||
|
(identical(other.editedAt, editedAt) ||
|
||||||
@@ -736,13 +801,13 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
aliasPrefix,
|
aliasPrefix,
|
||||||
const DeepCollectionEquality().hash(_tags),
|
const DeepCollectionEquality().hash(_tags),
|
||||||
const DeepCollectionEquality().hash(_categories),
|
const DeepCollectionEquality().hash(_categories),
|
||||||
const DeepCollectionEquality().hash(replies),
|
const DeepCollectionEquality().hash(_replies),
|
||||||
const DeepCollectionEquality().hash(replyId),
|
replyId,
|
||||||
const DeepCollectionEquality().hash(repostId),
|
repostId,
|
||||||
const DeepCollectionEquality().hash(replyTo),
|
replyTo,
|
||||||
const DeepCollectionEquality().hash(repostTo),
|
repostTo,
|
||||||
const DeepCollectionEquality().hash(visibleUsersList),
|
const DeepCollectionEquality().hash(_visibleUsersList),
|
||||||
const DeepCollectionEquality().hash(invisibleUsersList),
|
const DeepCollectionEquality().hash(_invisibleUsersList),
|
||||||
visibility,
|
visibility,
|
||||||
editedAt,
|
editedAt,
|
||||||
pinnedAt,
|
pinnedAt,
|
||||||
@@ -787,13 +852,13 @@ abstract class _SnPost extends SnPost {
|
|||||||
required final String? aliasPrefix,
|
required final String? aliasPrefix,
|
||||||
required final List<dynamic> tags,
|
required final List<dynamic> tags,
|
||||||
required final List<dynamic> categories,
|
required final List<dynamic> categories,
|
||||||
required final dynamic replies,
|
required final List<SnPost>? replies,
|
||||||
required final dynamic replyId,
|
required final int? replyId,
|
||||||
required final dynamic repostId,
|
required final int? repostId,
|
||||||
required final dynamic replyTo,
|
required final SnPost? replyTo,
|
||||||
required final dynamic repostTo,
|
required final SnPost? repostTo,
|
||||||
required final dynamic visibleUsersList,
|
required final List<int>? visibleUsersList,
|
||||||
required final dynamic invisibleUsersList,
|
required final List<int>? invisibleUsersList,
|
||||||
required final int visibility,
|
required final int visibility,
|
||||||
required final DateTime? editedAt,
|
required final DateTime? editedAt,
|
||||||
required final DateTime? pinnedAt,
|
required final DateTime? pinnedAt,
|
||||||
@@ -834,19 +899,19 @@ abstract class _SnPost extends SnPost {
|
|||||||
@override
|
@override
|
||||||
List<dynamic> get categories;
|
List<dynamic> get categories;
|
||||||
@override
|
@override
|
||||||
dynamic get replies;
|
List<SnPost>? get replies;
|
||||||
@override
|
@override
|
||||||
dynamic get replyId;
|
int? get replyId;
|
||||||
@override
|
@override
|
||||||
dynamic get repostId;
|
int? get repostId;
|
||||||
@override
|
@override
|
||||||
dynamic get replyTo;
|
SnPost? get replyTo;
|
||||||
@override
|
@override
|
||||||
dynamic get repostTo;
|
SnPost? get repostTo;
|
||||||
@override
|
@override
|
||||||
dynamic get visibleUsersList;
|
List<int>? get visibleUsersList;
|
||||||
@override
|
@override
|
||||||
dynamic get invisibleUsersList;
|
List<int>? get invisibleUsersList;
|
||||||
@override
|
@override
|
||||||
int get visibility;
|
int get visibility;
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -20,13 +20,23 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
|||||||
aliasPrefix: json['alias_prefix'] as String?,
|
aliasPrefix: json['alias_prefix'] as String?,
|
||||||
tags: json['tags'] as List<dynamic>,
|
tags: json['tags'] as List<dynamic>,
|
||||||
categories: json['categories'] as List<dynamic>,
|
categories: json['categories'] as List<dynamic>,
|
||||||
replies: json['replies'],
|
replies: (json['replies'] as List<dynamic>?)
|
||||||
replyId: json['reply_id'],
|
?.map((e) => SnPost.fromJson(e as Map<String, dynamic>))
|
||||||
repostId: json['repost_id'],
|
.toList(),
|
||||||
replyTo: json['reply_to'],
|
replyId: (json['reply_id'] as num?)?.toInt(),
|
||||||
repostTo: json['repost_to'],
|
repostId: (json['repost_id'] as num?)?.toInt(),
|
||||||
visibleUsersList: json['visible_users_list'],
|
replyTo: json['reply_to'] == null
|
||||||
invisibleUsersList: json['invisible_users_list'],
|
? null
|
||||||
|
: SnPost.fromJson(json['reply_to'] as Map<String, dynamic>),
|
||||||
|
repostTo: json['repost_to'] == null
|
||||||
|
? null
|
||||||
|
: SnPost.fromJson(json['repost_to'] as Map<String, dynamic>),
|
||||||
|
visibleUsersList: (json['visible_users_list'] as List<dynamic>?)
|
||||||
|
?.map((e) => (e as num).toInt())
|
||||||
|
.toList(),
|
||||||
|
invisibleUsersList: (json['invisible_users_list'] as List<dynamic>?)
|
||||||
|
?.map((e) => (e as num).toInt())
|
||||||
|
.toList(),
|
||||||
visibility: (json['visibility'] as num).toInt(),
|
visibility: (json['visibility'] as num).toInt(),
|
||||||
editedAt: json['edited_at'] == null
|
editedAt: json['edited_at'] == null
|
||||||
? null
|
? null
|
||||||
@@ -68,11 +78,11 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
|||||||
'alias_prefix': instance.aliasPrefix,
|
'alias_prefix': instance.aliasPrefix,
|
||||||
'tags': instance.tags,
|
'tags': instance.tags,
|
||||||
'categories': instance.categories,
|
'categories': instance.categories,
|
||||||
'replies': instance.replies,
|
'replies': instance.replies?.map((e) => e.toJson()).toList(),
|
||||||
'reply_id': instance.replyId,
|
'reply_id': instance.replyId,
|
||||||
'repost_id': instance.repostId,
|
'repost_id': instance.repostId,
|
||||||
'reply_to': instance.replyTo,
|
'reply_to': instance.replyTo?.toJson(),
|
||||||
'repost_to': instance.repostTo,
|
'repost_to': instance.repostTo?.toJson(),
|
||||||
'visible_users_list': instance.visibleUsersList,
|
'visible_users_list': instance.visibleUsersList,
|
||||||
'invisible_users_list': instance.invisibleUsersList,
|
'invisible_users_list': instance.invisibleUsersList,
|
||||||
'visibility': instance.visibility,
|
'visibility': instance.visibility,
|
||||||
|
|||||||
17
lib/types/websocket.dart
Normal file
17
lib/types/websocket.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'websocket.freezed.dart';
|
||||||
|
part 'websocket.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class WebSocketPackage with _$WebSocketPackage {
|
||||||
|
const factory WebSocketPackage({
|
||||||
|
@JsonKey(name: 'w') @Default('unknown') String method,
|
||||||
|
@JsonKey(name: 'e') String? endpoint,
|
||||||
|
@JsonKey(name: 'm') String? message,
|
||||||
|
@JsonKey(name: 'p') @Default({}) Map<String, dynamic>? payload,
|
||||||
|
}) = _WebSocketPackage;
|
||||||
|
|
||||||
|
factory WebSocketPackage.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$WebSocketPackageFromJson(json);
|
||||||
|
}
|
||||||
252
lib/types/websocket.freezed.dart
Normal file
252
lib/types/websocket.freezed.dart
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'websocket.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
WebSocketPackage _$WebSocketPackageFromJson(Map<String, dynamic> json) {
|
||||||
|
return _WebSocketPackage.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$WebSocketPackage {
|
||||||
|
@JsonKey(name: 'w')
|
||||||
|
String get method => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(name: 'e')
|
||||||
|
String? get endpoint => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(name: 'm')
|
||||||
|
String? get message => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(name: 'p')
|
||||||
|
Map<String, dynamic>? get payload => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this WebSocketPackage to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of WebSocketPackage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$WebSocketPackageCopyWith<WebSocketPackage> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $WebSocketPackageCopyWith<$Res> {
|
||||||
|
factory $WebSocketPackageCopyWith(
|
||||||
|
WebSocketPackage value, $Res Function(WebSocketPackage) then) =
|
||||||
|
_$WebSocketPackageCopyWithImpl<$Res, WebSocketPackage>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{@JsonKey(name: 'w') String method,
|
||||||
|
@JsonKey(name: 'e') String? endpoint,
|
||||||
|
@JsonKey(name: 'm') String? message,
|
||||||
|
@JsonKey(name: 'p') Map<String, dynamic>? payload});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$WebSocketPackageCopyWithImpl<$Res, $Val extends WebSocketPackage>
|
||||||
|
implements $WebSocketPackageCopyWith<$Res> {
|
||||||
|
_$WebSocketPackageCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of WebSocketPackage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? method = null,
|
||||||
|
Object? endpoint = freezed,
|
||||||
|
Object? message = freezed,
|
||||||
|
Object? payload = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
method: null == method
|
||||||
|
? _value.method
|
||||||
|
: method // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
endpoint: freezed == endpoint
|
||||||
|
? _value.endpoint
|
||||||
|
: endpoint // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
message: freezed == message
|
||||||
|
? _value.message
|
||||||
|
: message // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
payload: freezed == payload
|
||||||
|
? _value.payload
|
||||||
|
: payload // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$WebSocketPackageImplCopyWith<$Res>
|
||||||
|
implements $WebSocketPackageCopyWith<$Res> {
|
||||||
|
factory _$$WebSocketPackageImplCopyWith(_$WebSocketPackageImpl value,
|
||||||
|
$Res Function(_$WebSocketPackageImpl) then) =
|
||||||
|
__$$WebSocketPackageImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{@JsonKey(name: 'w') String method,
|
||||||
|
@JsonKey(name: 'e') String? endpoint,
|
||||||
|
@JsonKey(name: 'm') String? message,
|
||||||
|
@JsonKey(name: 'p') Map<String, dynamic>? payload});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$WebSocketPackageImplCopyWithImpl<$Res>
|
||||||
|
extends _$WebSocketPackageCopyWithImpl<$Res, _$WebSocketPackageImpl>
|
||||||
|
implements _$$WebSocketPackageImplCopyWith<$Res> {
|
||||||
|
__$$WebSocketPackageImplCopyWithImpl(_$WebSocketPackageImpl _value,
|
||||||
|
$Res Function(_$WebSocketPackageImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of WebSocketPackage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? method = null,
|
||||||
|
Object? endpoint = freezed,
|
||||||
|
Object? message = freezed,
|
||||||
|
Object? payload = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$WebSocketPackageImpl(
|
||||||
|
method: null == method
|
||||||
|
? _value.method
|
||||||
|
: method // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
endpoint: freezed == endpoint
|
||||||
|
? _value.endpoint
|
||||||
|
: endpoint // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
message: freezed == message
|
||||||
|
? _value.message
|
||||||
|
: message // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
payload: freezed == payload
|
||||||
|
? _value._payload
|
||||||
|
: payload // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$WebSocketPackageImpl implements _WebSocketPackage {
|
||||||
|
const _$WebSocketPackageImpl(
|
||||||
|
{@JsonKey(name: 'w') this.method = 'unknown',
|
||||||
|
@JsonKey(name: 'e') this.endpoint,
|
||||||
|
@JsonKey(name: 'm') this.message,
|
||||||
|
@JsonKey(name: 'p') final Map<String, dynamic>? payload = const {}})
|
||||||
|
: _payload = payload;
|
||||||
|
|
||||||
|
factory _$WebSocketPackageImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$WebSocketPackageImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'w')
|
||||||
|
final String method;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'e')
|
||||||
|
final String? endpoint;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'm')
|
||||||
|
final String? message;
|
||||||
|
final Map<String, dynamic>? _payload;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'p')
|
||||||
|
Map<String, dynamic>? get payload {
|
||||||
|
final value = _payload;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_payload is EqualUnmodifiableMapView) return _payload;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebSocketPackage(method: $method, endpoint: $endpoint, message: $message, payload: $payload)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$WebSocketPackageImpl &&
|
||||||
|
(identical(other.method, method) || other.method == method) &&
|
||||||
|
(identical(other.endpoint, endpoint) ||
|
||||||
|
other.endpoint == endpoint) &&
|
||||||
|
(identical(other.message, message) || other.message == message) &&
|
||||||
|
const DeepCollectionEquality().equals(other._payload, _payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, method, endpoint, message,
|
||||||
|
const DeepCollectionEquality().hash(_payload));
|
||||||
|
|
||||||
|
/// Create a copy of WebSocketPackage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$WebSocketPackageImplCopyWith<_$WebSocketPackageImpl> get copyWith =>
|
||||||
|
__$$WebSocketPackageImplCopyWithImpl<_$WebSocketPackageImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$WebSocketPackageImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _WebSocketPackage implements WebSocketPackage {
|
||||||
|
const factory _WebSocketPackage(
|
||||||
|
{@JsonKey(name: 'w') final String method,
|
||||||
|
@JsonKey(name: 'e') final String? endpoint,
|
||||||
|
@JsonKey(name: 'm') final String? message,
|
||||||
|
@JsonKey(name: 'p') final Map<String, dynamic>? payload}) =
|
||||||
|
_$WebSocketPackageImpl;
|
||||||
|
|
||||||
|
factory _WebSocketPackage.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$WebSocketPackageImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'w')
|
||||||
|
String get method;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'e')
|
||||||
|
String? get endpoint;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'm')
|
||||||
|
String? get message;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'p')
|
||||||
|
Map<String, dynamic>? get payload;
|
||||||
|
|
||||||
|
/// Create a copy of WebSocketPackage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$WebSocketPackageImplCopyWith<_$WebSocketPackageImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
25
lib/types/websocket.g.dart
Normal file
25
lib/types/websocket.g.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'websocket.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$WebSocketPackageImpl _$$WebSocketPackageImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$WebSocketPackageImpl(
|
||||||
|
method: json['w'] as String? ?? 'unknown',
|
||||||
|
endpoint: json['e'] as String?,
|
||||||
|
message: json['m'] as String?,
|
||||||
|
payload: json['p'] as Map<String, dynamic>? ?? const {},
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$WebSocketPackageImplToJson(
|
||||||
|
_$WebSocketPackageImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'w': instance.method,
|
||||||
|
'e': instance.endpoint,
|
||||||
|
'm': instance.message,
|
||||||
|
'p': instance.payload,
|
||||||
|
};
|
||||||
@@ -28,6 +28,9 @@ class AttachmentDetailPopup extends StatelessWidget {
|
|||||||
tag: 'attachment-${data.rid}-${heroTag ?? uuid.v4()}',
|
tag: 'attachment-${data.rid}-${heroTag ?? uuid.v4()}',
|
||||||
child: PhotoView(
|
child: PhotoView(
|
||||||
key: Key('attachment-detail-${data.rid}-$heroTag'),
|
key: Key('attachment-detail-${data.rid}-$heroTag'),
|
||||||
|
backgroundDecoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.7),
|
||||||
|
),
|
||||||
imageProvider: UniversalImage.provider(
|
imageProvider: UniversalImage.provider(
|
||||||
sn.getAttachmentUrl(data.rid),
|
sn.getAttachmentUrl(data.rid),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_detail.dart';
|
import 'package:surface/widgets/attachment/attachment_detail.dart';
|
||||||
@@ -23,15 +29,11 @@ class AttachmentItem extends StatelessWidget {
|
|||||||
case 'image':
|
case 'image':
|
||||||
return Hero(
|
return Hero(
|
||||||
tag: 'attachment-${data.rid}-$heroTag',
|
tag: 'attachment-${data.rid}-$heroTag',
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
child: AutoResizeUniversalImage(
|
||||||
return UniversalImage(
|
|
||||||
sn.getAttachmentUrl(data.rid),
|
sn.getAttachmentUrl(data.rid),
|
||||||
key: Key('attachment-${data.rid}-$heroTag'),
|
key: Key('attachment-${data.rid}-$heroTag'),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
cacheHeight: constraints.maxHeight,
|
),
|
||||||
cacheWidth: constraints.maxWidth,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return const Placeholder();
|
return const Placeholder();
|
||||||
@@ -43,6 +45,12 @@ class AttachmentItem extends StatelessWidget {
|
|||||||
final uuid = Uuid();
|
final uuid = Uuid();
|
||||||
final heroTag = uuid.v4();
|
final heroTag = uuid.v4();
|
||||||
|
|
||||||
|
if (data.isMature) {
|
||||||
|
return _AttachmentItemSensitiveBlur(
|
||||||
|
child: _buildContent(context, heroTag),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isExpandable) {
|
if (isExpandable) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: _buildContent(context, heroTag),
|
child: _buildContent(context, heroTag),
|
||||||
@@ -58,3 +66,87 @@ class AttachmentItem extends StatelessWidget {
|
|||||||
return _buildContent(context, heroTag);
|
return _buildContent(context, heroTag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _AttachmentItemSensitiveBlur extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
const _AttachmentItemSensitiveBlur({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_AttachmentItemSensitiveBlur> createState() =>
|
||||||
|
_AttachmentItemSensitiveBlurState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AttachmentItemSensitiveBlurState
|
||||||
|
extends State<_AttachmentItemSensitiveBlur> {
|
||||||
|
bool _doesShow = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
widget.child,
|
||||||
|
ClipRect(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 40, sigmaY: 40),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Symbols.visibility_off,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text('sensitiveContent')
|
||||||
|
.tr()
|
||||||
|
.fontSize(20)
|
||||||
|
.textColor(Colors.white)
|
||||||
|
.bold(),
|
||||||
|
Text('sensitiveContentDescription')
|
||||||
|
.tr()
|
||||||
|
.fontSize(14)
|
||||||
|
.textColor(Colors.white.withOpacity(0.8)),
|
||||||
|
const Gap(16),
|
||||||
|
InkWell(
|
||||||
|
child: Text('sensitiveContentReveal')
|
||||||
|
.tr()
|
||||||
|
.textColor(Colors.white),
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _doesShow = !_doesShow);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.opacity(_doesShow ? 0 : 1, animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||||
|
if (_doesShow)
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
child: InkWell(
|
||||||
|
child: Icon(
|
||||||
|
Symbols.visibility_off,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: Offset(0, 1.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 12),
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _doesShow = !_doesShow);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@@ -10,15 +8,16 @@ import 'package:surface/widgets/attachment/attachment_item.dart';
|
|||||||
class AttachmentList extends StatelessWidget {
|
class AttachmentList extends StatelessWidget {
|
||||||
final List<SnAttachment> data;
|
final List<SnAttachment> data;
|
||||||
final bool? bordered;
|
final bool? bordered;
|
||||||
final double? maxListHeight;
|
final double? maxHeight;
|
||||||
|
final EdgeInsets? listPadding;
|
||||||
const AttachmentList({
|
const AttachmentList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.data,
|
required this.data,
|
||||||
this.bordered,
|
this.bordered,
|
||||||
this.maxListHeight,
|
this.maxHeight,
|
||||||
|
this.listPadding,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const double kMaxListItemWidth = 520;
|
|
||||||
static const BorderRadius kDefaultRadius =
|
static const BorderRadius kDefaultRadius =
|
||||||
BorderRadius.all(Radius.circular(8));
|
BorderRadius.all(Radius.circular(8));
|
||||||
|
|
||||||
@@ -27,18 +26,22 @@ class AttachmentList extends StatelessWidget {
|
|||||||
final borderSide = (bordered ?? false)
|
final borderSide = (bordered ?? false)
|
||||||
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
|
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
|
||||||
: BorderSide.none;
|
: BorderSide.none;
|
||||||
|
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
|
||||||
|
final constraints = BoxConstraints(
|
||||||
|
minWidth: 80,
|
||||||
|
maxHeight: maxHeight ?? double.infinity,
|
||||||
|
);
|
||||||
|
|
||||||
if (data.isEmpty) return const SizedBox.shrink();
|
if (data.isEmpty) return const SizedBox.shrink();
|
||||||
if (data.length == 1) {
|
if (data.length == 1) {
|
||||||
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
||||||
return Container(
|
return Padding(
|
||||||
constraints: BoxConstraints(
|
// Single child list-like displaying
|
||||||
maxWidth: math.min(
|
padding: listPadding ?? EdgeInsets.zero,
|
||||||
MediaQuery.of(context).size.width - 20,
|
child: Container(
|
||||||
kMaxListItemWidth,
|
constraints: constraints,
|
||||||
),
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
border: Border(top: borderSide, bottom: borderSide),
|
border: Border(top: borderSide, bottom: borderSide),
|
||||||
borderRadius: kDefaultRadius,
|
borderRadius: kDefaultRadius,
|
||||||
),
|
),
|
||||||
@@ -49,11 +52,13 @@ class AttachmentList extends StatelessWidget {
|
|||||||
child: AttachmentItem(data: data[0], isExpandable: true),
|
child: AttachmentItem(data: data[0], isExpandable: true),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
border: Border(top: borderSide, bottom: borderSide),
|
border: Border(top: borderSide, bottom: borderSide),
|
||||||
),
|
),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
@@ -64,21 +69,19 @@ class AttachmentList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: BoxConstraints(maxHeight: maxListHeight ?? 320),
|
constraints: BoxConstraints(maxHeight: maxHeight ?? 320),
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior: _AttachmentListScrollBehavior(),
|
behavior: _AttachmentListScrollBehavior(),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: data.length,
|
itemCount: data.length,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return Container(
|
return Stack(
|
||||||
constraints: BoxConstraints(
|
children: [
|
||||||
maxWidth: math.min(
|
Container(
|
||||||
MediaQuery.of(context).size.width - 20,
|
constraints: constraints,
|
||||||
kMaxListItemWidth,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor,
|
||||||
border: Border(top: borderSide, bottom: borderSide),
|
border: Border(top: borderSide, bottom: borderSide),
|
||||||
borderRadius: kDefaultRadius,
|
borderRadius: kDefaultRadius,
|
||||||
),
|
),
|
||||||
@@ -86,13 +89,23 @@ class AttachmentList extends StatelessWidget {
|
|||||||
aspectRatio: data[idx].metadata['ratio']?.toDouble() ?? 1,
|
aspectRatio: data[idx].metadata['ratio']?.toDouble() ?? 1,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: kDefaultRadius,
|
borderRadius: kDefaultRadius,
|
||||||
child: AttachmentItem(data: data[idx], isExpandable: true),
|
child:
|
||||||
|
AttachmentItem(data: data[idx], isExpandable: true),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 12,
|
||||||
|
bottom: 12,
|
||||||
|
child: Chip(
|
||||||
|
label: Text('${idx + 1}/${data.length}'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) => const Gap(8),
|
separatorBuilder: (context, index) => const Gap(8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: listPadding,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
),
|
),
|
||||||
|
|||||||
55
lib/widgets/connection_indicator.dart
Normal file
55
lib/widgets/connection_indicator.dart
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/providers/websocket.dart';
|
||||||
|
|
||||||
|
class ConnectionIndicator extends StatelessWidget {
|
||||||
|
const ConnectionIndicator({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ws = context.watch<WebSocketProvider>();
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: ws,
|
||||||
|
builder: (context, _) {
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: 8,
|
||||||
|
top: MediaQuery.of(context).padding.top + 8,
|
||||||
|
left: 24,
|
||||||
|
right: 24,
|
||||||
|
),
|
||||||
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
child: ua.isAuthorized
|
||||||
|
? Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (ws.isBusy)
|
||||||
|
Text('serverConnecting').tr().textColor(
|
||||||
|
Theme.of(context).colorScheme.onSecondaryContainer)
|
||||||
|
else if (!ws.isConnected)
|
||||||
|
Text('serverDisconnected').tr().textColor(
|
||||||
|
Theme.of(context).colorScheme.onSecondaryContainer),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
)
|
||||||
|
.height(
|
||||||
|
(ws.isBusy || !ws.isConnected) && ua.isAuthorized
|
||||||
|
? MediaQuery.of(context).padding.top + 30
|
||||||
|
: 0,
|
||||||
|
animate: true)
|
||||||
|
.animate(
|
||||||
|
const Duration(milliseconds: 300),
|
||||||
|
Curves.easeInOut,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:surface/widgets/navigation/app_destinations.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/navigation.dart';
|
||||||
|
|
||||||
class AppBottomNavigationBar extends StatefulWidget {
|
class AppBottomNavigationBar extends StatefulWidget {
|
||||||
const AppBottomNavigationBar({super.key});
|
const AppBottomNavigationBar({super.key});
|
||||||
@@ -10,23 +12,46 @@ class AppBottomNavigationBar extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppBottomNavigationBarState extends State<AppBottomNavigationBar> {
|
class _AppBottomNavigationBarState extends State<AppBottomNavigationBar> {
|
||||||
int _currentIndex = 0;
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context
|
||||||
|
.read<NavigationProvider>()
|
||||||
|
.autoDetectIndex(GoRouter.maybeOf(context));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final nav = context.watch<NavigationProvider>();
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: nav,
|
||||||
|
builder: (context, _) {
|
||||||
|
if (!nav.isIndexInRange(0, nav.pinnedDestinationCount)) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final destinations = [
|
||||||
|
...nav.destinations.where((ele) => ele.isPinned),
|
||||||
|
];
|
||||||
|
|
||||||
return BottomNavigationBar(
|
return BottomNavigationBar(
|
||||||
currentIndex: _currentIndex,
|
currentIndex: nav.getIndexInRange(0, nav.pinnedDestinationCount),
|
||||||
type: BottomNavigationBarType.fixed,
|
type: BottomNavigationBarType.fixed,
|
||||||
showUnselectedLabels: false,
|
showUnselectedLabels: false,
|
||||||
items: appDestinations.map((ele) {
|
items: destinations.map((ele) {
|
||||||
return BottomNavigationBarItem(
|
return BottomNavigationBarItem(
|
||||||
icon: ele.icon,
|
icon: ele.icon,
|
||||||
label: ele.label,
|
label: ele.label.tr(),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onTap: (idx) {
|
onTap: (idx) {
|
||||||
setState(() => _currentIndex = idx);
|
nav.setIndex(idx);
|
||||||
GoRouter.of(context).goNamed(appDestinations[idx].screen);
|
GoRouter.of(context).goNamed(destinations[idx].screen);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
|
||||||
|
|
||||||
class AppNavDestination {
|
|
||||||
final String label;
|
|
||||||
final String screen;
|
|
||||||
final Widget icon;
|
|
||||||
|
|
||||||
AppNavDestination({
|
|
||||||
required this.label,
|
|
||||||
required this.screen,
|
|
||||||
required this.icon,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AppNavDestination> appDestinations = [
|
|
||||||
AppNavDestination(
|
|
||||||
icon: Icon(Symbols.home, weight: 400, opticalSize: 20),
|
|
||||||
screen: 'home',
|
|
||||||
label: tr('screenHome'),
|
|
||||||
),
|
|
||||||
AppNavDestination(
|
|
||||||
icon: Icon(Symbols.explore, weight: 400, opticalSize: 20),
|
|
||||||
screen: 'explore',
|
|
||||||
label: tr('screenExplore'),
|
|
||||||
),
|
|
||||||
AppNavDestination(
|
|
||||||
icon: Icon(Symbols.account_circle, weight: 400, opticalSize: 20),
|
|
||||||
screen: 'account',
|
|
||||||
label: tr('screenAccount'),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
85
lib/widgets/navigation/app_drawer_navigation.dart
Normal file
85
lib/widgets/navigation/app_drawer_navigation.dart
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/navigation.dart';
|
||||||
|
|
||||||
|
class AppNavigationDrawer extends StatefulWidget {
|
||||||
|
final double? elevation;
|
||||||
|
const AppNavigationDrawer({super.key, this.elevation});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppNavigationDrawer> createState() => _AppNavigationDrawerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context
|
||||||
|
.read<NavigationProvider>()
|
||||||
|
.autoDetectIndex(GoRouter.maybeOf(context));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final nav = context.watch<NavigationProvider>();
|
||||||
|
|
||||||
|
final backgroundColor = ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
||||||
|
? Theme.of(context).colorScheme.surface
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: nav,
|
||||||
|
builder: (context, _) {
|
||||||
|
final destinations = [
|
||||||
|
...nav.destinations.where((ele) => ele.isPinned),
|
||||||
|
...nav.destinations.where((ele) => !ele.isPinned),
|
||||||
|
];
|
||||||
|
|
||||||
|
return NavigationDrawer(
|
||||||
|
elevation: widget.elevation,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
selectedIndex: nav.currentIndex,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Solar Network').bold(),
|
||||||
|
Text('Canary Preview 2.0α').fontSize(12).textColor(
|
||||||
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.5)),
|
||||||
|
],
|
||||||
|
).padding(
|
||||||
|
horizontal: 32,
|
||||||
|
top: MediaQuery.of(context).padding.top > 16 ? 8 : 16,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
...destinations.where((ele) => ele.isPinned).map((ele) {
|
||||||
|
return NavigationDrawerDestination(
|
||||||
|
icon: ele.icon,
|
||||||
|
label: Text(ele.label).tr(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const Divider(),
|
||||||
|
...destinations.where((ele) => !ele.isPinned).map((ele) {
|
||||||
|
return NavigationDrawerDestination(
|
||||||
|
icon: ele.icon,
|
||||||
|
label: Text(ele.label).tr(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (idx) {
|
||||||
|
nav.setIndex(idx);
|
||||||
|
GoRouter.of(context).goNamed(destinations[idx].screen);
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
lib/widgets/navigation/app_rail_navigation.dart
Normal file
68
lib/widgets/navigation/app_rail_navigation.dart
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/navigation.dart';
|
||||||
|
|
||||||
|
class AppRailNavigation extends StatefulWidget {
|
||||||
|
const AppRailNavigation({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppRailNavigation> createState() => _AppRailNavigationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppRailNavigationState extends State<AppRailNavigation> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context
|
||||||
|
.read<NavigationProvider>()
|
||||||
|
.autoDetectIndex(GoRouter.maybeOf(context));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final nav = context.watch<NavigationProvider>();
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: nav,
|
||||||
|
builder: (context, _) {
|
||||||
|
final destinations =
|
||||||
|
nav.destinations.where((ele) => ele.isPinned).toList();
|
||||||
|
|
||||||
|
return NavigationRail(
|
||||||
|
selectedIndex: nav.currentIndex,
|
||||||
|
destinations: [
|
||||||
|
...destinations.where((ele) => ele.isPinned).map((ele) {
|
||||||
|
return NavigationRailDestination(
|
||||||
|
icon: ele.icon,
|
||||||
|
label: Text(ele.label).tr(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
trailing: Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: StyledWidget(
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.menu),
|
||||||
|
onPressed: () {
|
||||||
|
Scaffold.of(context).openDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).padding(bottom: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDestinationSelected: (idx) {
|
||||||
|
nav.setIndex(idx);
|
||||||
|
GoRouter.of(context).goNamed(destinations[idx].screen);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,26 +2,23 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
|
import 'package:surface/widgets/connection_indicator.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_background.dart';
|
import 'package:surface/widgets/navigation/app_background.dart';
|
||||||
import 'package:surface/widgets/navigation/app_bottom_navigation.dart';
|
import 'package:surface/widgets/navigation/app_bottom_navigation.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_drawer_navigation.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_rail_navigation.dart';
|
||||||
|
|
||||||
class AppScaffold extends StatelessWidget {
|
class AppScaffold extends StatelessWidget {
|
||||||
final PreferredSizeWidget? appBar;
|
|
||||||
final FloatingActionButtonLocation? floatingActionButtonLocation;
|
|
||||||
final Widget? floatingActionButton;
|
|
||||||
final String? title;
|
final String? title;
|
||||||
final Widget? body;
|
final Widget? body;
|
||||||
final bool autoImplyAppBar;
|
final bool showAppBar;
|
||||||
final bool showBottomNavigation;
|
final bool showBottomNavigation;
|
||||||
const AppScaffold({
|
const AppScaffold({
|
||||||
super.key,
|
super.key,
|
||||||
this.appBar,
|
|
||||||
this.floatingActionButton,
|
|
||||||
this.floatingActionButtonLocation,
|
|
||||||
this.title,
|
this.title,
|
||||||
this.body,
|
this.body,
|
||||||
this.autoImplyAppBar = false,
|
this.showAppBar = true,
|
||||||
this.showBottomNavigation = false,
|
this.showBottomNavigation = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,26 +29,65 @@ class AppScaffold extends StatelessWidget {
|
|||||||
: false;
|
: false;
|
||||||
|
|
||||||
final state = GoRouter.maybeOf(context);
|
final state = GoRouter.maybeOf(context);
|
||||||
|
final autoTitle = state != null
|
||||||
|
? 'screen${state.routerDelegate.currentConfiguration.last.route.name?.capitalize()}'
|
||||||
|
: 'screen';
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: showAppBar
|
||||||
|
? AppBar(
|
||||||
|
title: Text(title ?? autoTitle.tr()),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
body: body,
|
||||||
|
bottomNavigationBar:
|
||||||
|
isShowBottomNavigation ? AppBottomNavigationBar() : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppRootScaffold extends StatelessWidget {
|
||||||
|
final Widget body;
|
||||||
|
const AppRootScaffold({super.key, required this.body});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
|
final isCollapseDrawer =
|
||||||
|
ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE);
|
||||||
|
final isExpandDrawer = ResponsiveBreakpoints.of(context).largerThan(TABLET);
|
||||||
|
|
||||||
|
final innerWidget = isCollapseDrawer
|
||||||
|
? body
|
||||||
|
: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
right: BorderSide(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / devicePixelRatio,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: isExpandDrawer
|
||||||
|
? AppNavigationDrawer(elevation: 0)
|
||||||
|
: AppRailNavigation(),
|
||||||
|
),
|
||||||
|
Expanded(child: body),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return AppBackground(
|
return AppBackground(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: appBar ??
|
body: Column(
|
||||||
(autoImplyAppBar
|
children: [
|
||||||
? AppBar(
|
ConnectionIndicator(),
|
||||||
title: title != null
|
Expanded(child: innerWidget),
|
||||||
? Text(title!)
|
],
|
||||||
: state != null
|
),
|
||||||
? Text(
|
drawer: !isExpandDrawer ? AppNavigationDrawer() : null,
|
||||||
('screen${state.routerDelegate.currentConfiguration.last.route.name?.capitalize()}')
|
|
||||||
.tr(),
|
|
||||||
)
|
|
||||||
: null)
|
|
||||||
: null),
|
|
||||||
body: body,
|
|
||||||
floatingActionButtonLocation: floatingActionButtonLocation,
|
|
||||||
floatingActionButton: floatingActionButton,
|
|
||||||
bottomNavigationBar:
|
|
||||||
isShowBottomNavigation ? AppBottomNavigationBar() : null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_mini_editor.dart';
|
import 'package:surface/widgets/post/post_mini_editor.dart';
|
||||||
@@ -14,7 +15,12 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
|||||||
|
|
||||||
class PostCommentSliverList extends StatefulWidget {
|
class PostCommentSliverList extends StatefulWidget {
|
||||||
final int parentPostId;
|
final int parentPostId;
|
||||||
const PostCommentSliverList({super.key, required this.parentPostId});
|
final double? maxWidth;
|
||||||
|
const PostCommentSliverList({
|
||||||
|
super.key,
|
||||||
|
required this.parentPostId,
|
||||||
|
this.maxWidth,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PostCommentSliverList> createState() => PostCommentSliverListState();
|
State<PostCommentSliverList> createState() => PostCommentSliverListState();
|
||||||
@@ -88,7 +94,12 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
onFetchData: _fetchPosts,
|
onFetchData: _fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: widget.maxWidth ?? double.infinity,
|
||||||
|
),
|
||||||
child: PostItem(data: _posts[idx]),
|
child: PostItem(data: _posts[idx]),
|
||||||
|
).center(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postDetail',
|
'postDetail',
|
||||||
@@ -121,6 +132,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final ua = context.watch<UserProvider>();
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -139,6 +151,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
|
if (ua.isAuthorized)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 240,
|
height: 240,
|
||||||
|
|||||||
@@ -38,10 +38,16 @@ class PostItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_PostContentHeader(data: data).padding(horizontal: 12, vertical: 8),
|
_PostContentHeader(data: data).padding(horizontal: 12, vertical: 8),
|
||||||
_PostContentBody(data: data.body).padding(horizontal: 16, bottom: 6),
|
_PostContentBody(data: data.body).padding(horizontal: 16, bottom: 6),
|
||||||
|
if (data.repostTo != null)
|
||||||
|
_PostQuoteContent(child: data.repostTo!).padding(
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
if (data.preload?.attachments?.isNotEmpty ?? true)
|
if (data.preload?.attachments?.isNotEmpty ?? true)
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
data: data.preload!.attachments!,
|
data: data.preload!.attachments!,
|
||||||
bordered: true,
|
bordered: true,
|
||||||
|
maxHeight: 520,
|
||||||
|
listPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
),
|
||||||
_PostBottomAction(
|
_PostBottomAction(
|
||||||
data: data,
|
data: data,
|
||||||
@@ -148,7 +154,13 @@ class _PostBottomAction extends StatelessWidget {
|
|||||||
|
|
||||||
class _PostContentHeader extends StatelessWidget {
|
class _PostContentHeader extends StatelessWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
const _PostContentHeader({required this.data});
|
final bool isCompact;
|
||||||
|
final bool showActions;
|
||||||
|
const _PostContentHeader({
|
||||||
|
required this.data,
|
||||||
|
this.isCompact = false,
|
||||||
|
this.showActions = true,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -157,8 +169,28 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
AccountImage(content: data.publisher.avatar),
|
AccountImage(
|
||||||
const Gap(12),
|
content: data.publisher.avatar,
|
||||||
|
radius: isCompact ? 12 : 20,
|
||||||
|
),
|
||||||
|
Gap(isCompact ? 8 : 12),
|
||||||
|
if (isCompact)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(data.publisher.nick).bold(),
|
||||||
|
const Gap(4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('@${data.publisher.name}').fontSize(13),
|
||||||
|
const Gap(4),
|
||||||
|
Text(RelativeTime(context).format(
|
||||||
|
data.publishedAt ?? data.createdAt,
|
||||||
|
)).fontSize(13),
|
||||||
|
],
|
||||||
|
).opacity(0.8),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -176,6 +208,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (showActions)
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
icon: const Icon(Symbols.more_horiz),
|
icon: const Icon(Symbols.more_horiz),
|
||||||
style: const ButtonStyle(
|
style: const ButtonStyle(
|
||||||
@@ -269,3 +302,29 @@ class _PostContentBody extends StatelessWidget {
|
|||||||
return MarkdownTextContent(content: data['content']);
|
return MarkdownTextContent(content: data['content']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostQuoteContent extends StatelessWidget {
|
||||||
|
final SnPost child;
|
||||||
|
const _PostQuoteContent({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_PostContentHeader(data: child, isCompact: true, showActions: false)
|
||||||
|
.padding(bottom: 4),
|
||||||
|
_PostContentBody(data: child.body),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,16 +7,16 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_secure_storage/flutter_secure_storage_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStoragePlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_plugin_register_with_registrar(flutter_secure_storage_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
flutter_secure_storage
|
flutter_secure_storage_linux
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Foundation
|
|||||||
|
|
||||||
import connectivity_plus
|
import connectivity_plus
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
|
import flutter_secure_storage_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
@@ -15,6 +16,7 @@ import url_launcher_macos
|
|||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
|||||||
106
pubspec.lock
106
pubspec.lock
@@ -5,23 +5,23 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "76.0.0"
|
version: "72.0.0"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.3.3"
|
version: "0.3.2"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.11.0"
|
version: "6.7.0"
|
||||||
animations:
|
animations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -202,10 +202,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.0"
|
version: "1.18.0"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -418,10 +418,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_linux
|
name: file_selector_linux
|
||||||
sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2"
|
sha256: b2b91daf8a68ecfa4a01b778a6f52edef9b14ecd506e771488ea0f2e0784198b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3"
|
version: "0.9.3+1"
|
||||||
file_selector_macos:
|
file_selector_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -532,10 +532,50 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_secure_storage
|
name: flutter_secure_storage
|
||||||
sha256: "9f3dd2ac3b6875b0fde5b04734789c3ef35ba3965c18e99dd564a7a2f8056df6"
|
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "9.2.2"
|
||||||
|
flutter_secure_storage_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_linux
|
||||||
|
sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
flutter_secure_storage_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_macos
|
||||||
|
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
flutter_secure_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_platform_interface
|
||||||
|
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_secure_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_web
|
||||||
|
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
flutter_secure_storage_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_windows
|
||||||
|
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
flutter_shaders:
|
flutter_shaders:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -598,10 +638,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: ce89c5a993ca5eea74535f798478502c30a625ecb10a1de4d7fef5cd1bcac2a4
|
sha256: "8ae664a70174163b9f65ea68dd8673e29db8f9095de7b5cd00e167c621f4fef5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.4.1"
|
version: "14.6.0"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -766,10 +806,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: js
|
name: js
|
||||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.1"
|
version: "0.6.7"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -790,18 +830,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.8"
|
version: "10.0.5"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.9"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -838,10 +878,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: macros
|
name: macros
|
||||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3-main.0"
|
version: "0.1.2-main.4"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1150,7 +1190,7 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.99"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1227,10 +1267,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1251,10 +1291,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.2.0"
|
||||||
styled_widget:
|
styled_widget:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1291,10 +1331,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.3"
|
version: "0.7.2"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1347,10 +1387,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
|
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1411,10 +1451,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.0"
|
version: "14.2.5"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1440,7 +1480,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.6"
|
version: "0.1.6"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.0+1
|
version: 2.0.0+4
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@@ -58,7 +58,7 @@ dependencies:
|
|||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
relative_time: ^5.0.0
|
relative_time: ^5.0.0
|
||||||
flutter_secure_storage: ^4.2.1
|
flutter_secure_storage: ^9.2.2
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
cross_file: ^0.3.4+2
|
cross_file: ^0.3.4+2
|
||||||
file_picker: ^8.1.3
|
file_picker: ^8.1.3
|
||||||
@@ -73,6 +73,7 @@ dependencies:
|
|||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
mime: ^2.0.0
|
mime: ^2.0.0
|
||||||
|
web_socket_channel: ^3.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
9
roadsign.toml
Normal file
9
roadsign.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
id = "solian-next"
|
||||||
|
|
||||||
|
[[locations]]
|
||||||
|
id = "solian-next"
|
||||||
|
host = ["sn-next.solsynth.dev"]
|
||||||
|
path = ["/"]
|
||||||
|
[[locations.destinations]]
|
||||||
|
id = "solian-next-web"
|
||||||
|
uri = "files:///workdir/solian-next?fallback=index.html&index=index.html"
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
@@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
connectivity_plus
|
connectivity_plus
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
|
flutter_secure_storage_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user