Compare commits
10 Commits
2.4.2+84
...
9cc577adbe
Author | SHA1 | Date | |
---|---|---|---|
|
9cc577adbe | ||
|
dd196b7754 | ||
|
16c07c2133 | ||
|
6bcb658d44 | ||
|
9311bfc3b5 | ||
|
8dd6435a30 | ||
|
21a1d4a2ad | ||
|
603875b1af | ||
|
4209a13c84 | ||
|
55b79bfd8f |
BIN
assets/icon/kanban-1st.jpg
Executable file
BIN
assets/icon/kanban-1st.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 509 KiB |
@@ -890,5 +890,30 @@
|
|||||||
},
|
},
|
||||||
"settingsHideBottomNav": "Hide Bottom Navigation",
|
"settingsHideBottomNav": "Hide Bottom Navigation",
|
||||||
"settingsHideBottomNavDescription": "Hide the bottom navigation bar, and show the navigation buttons in the drawer.",
|
"settingsHideBottomNavDescription": "Hide the bottom navigation bar, and show the navigation buttons in the drawer.",
|
||||||
"reCaptcha": "reCaptcha"
|
"reCaptcha": "reCaptcha",
|
||||||
|
"friends": "Friends",
|
||||||
|
"friendsDescription": "Manage your friendships.",
|
||||||
|
"album": "Album",
|
||||||
|
"albumDescription": "View albums and manage attachments.",
|
||||||
|
"stickers": "Stickers",
|
||||||
|
"stickersDescription": "View sticker packs and manage stickers.",
|
||||||
|
"navBottomUnauthorizedCaption": "Or create an account",
|
||||||
|
"walletCurrencyGoldenShort": "GDP",
|
||||||
|
"walletCurrencyGolden": {
|
||||||
|
"one": "{} Golden Point",
|
||||||
|
"other": "{} Golden Points"
|
||||||
|
},
|
||||||
|
"walletTransactionTypeNormal": "Source Point",
|
||||||
|
"walletTransactionTypeGolden": "Golden Point",
|
||||||
|
"accountProgram": "Programs",
|
||||||
|
"accountProgramDescription": "Explore the available member programs.",
|
||||||
|
"accountProgramJoin": "Join Program",
|
||||||
|
"accountProgramJoinRequirements": "Requirements",
|
||||||
|
"accountProgramJoinPricing": "Pricing",
|
||||||
|
"accountProgramJoinPricingHint": "Billed every (30 days) month.",
|
||||||
|
"accountProgramLeaveHint": "After leaving the program, the source points will not be refunded.",
|
||||||
|
"accountProgramJoined": "Joined Program.",
|
||||||
|
"accountProgramAlreadyJoined": "Joined",
|
||||||
|
"accountProgramLeft": "Left Program.",
|
||||||
|
"leave": "Leave"
|
||||||
}
|
}
|
||||||
|
@@ -888,5 +888,30 @@
|
|||||||
},
|
},
|
||||||
"settingsHideBottomNav": "隐藏底部导航栏",
|
"settingsHideBottomNav": "隐藏底部导航栏",
|
||||||
"settingsHideBottomNavDescription": "隐藏底部导航栏,在侧边栏抽屉显示导航按钮。",
|
"settingsHideBottomNavDescription": "隐藏底部导航栏,在侧边栏抽屉显示导航按钮。",
|
||||||
"reCaptcha": "人机验证"
|
"reCaptcha": "人机验证",
|
||||||
|
"friends": "好友",
|
||||||
|
"friendsDescription": "管理好友关系。",
|
||||||
|
"album": "相册",
|
||||||
|
"albumDescription": "查看相册与管理上传附件。",
|
||||||
|
"stickers": "贴图",
|
||||||
|
"stickersDescription": "查看贴图包与管理贴图。",
|
||||||
|
"navBottomUnauthorizedCaption": "或者注册一个账号",
|
||||||
|
"walletCurrencyGoldenShort": "金点",
|
||||||
|
"walletCurrencyGolden": {
|
||||||
|
"one": "{} 金点",
|
||||||
|
"other": "{} 金点"
|
||||||
|
},
|
||||||
|
"walletTransactionTypeNormal": "源点",
|
||||||
|
"walletTransactionTypeGolden": "金点",
|
||||||
|
"accountProgram": "计划",
|
||||||
|
"accountProgramDescription": "了解可用的成员计划。",
|
||||||
|
"accountProgramJoin": "加入计划",
|
||||||
|
"accountProgramJoinRequirements": "要求",
|
||||||
|
"accountProgramJoinPricing": "价格",
|
||||||
|
"accountProgramJoinPricingHint": "按月(30 天)收费",
|
||||||
|
"accountProgramLeaveHint": "离开计划后,之前花费的源点不会退款。",
|
||||||
|
"accountProgramJoined": "已加入计划。",
|
||||||
|
"accountProgramLeft": "已离开计划。",
|
||||||
|
"accountProgramAlreadyJoined": "已加入",
|
||||||
|
"leave": "离开"
|
||||||
}
|
}
|
||||||
|
@@ -888,5 +888,12 @@
|
|||||||
},
|
},
|
||||||
"settingsHideBottomNav": "隱藏底部導航欄",
|
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||||
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||||
"reCaptcha": "人機驗證"
|
"reCaptcha": "人機驗證",
|
||||||
|
"friends": "好友",
|
||||||
|
"friendsDescription": "管理好友關係。",
|
||||||
|
"album": "相冊",
|
||||||
|
"albumDescription": "查看相冊與管理上傳附件。",
|
||||||
|
"stickers": "貼圖",
|
||||||
|
"stickersDescription": "查看貼圖包與管理貼圖。",
|
||||||
|
"navBottomUnauthorizedCaption": "或者註冊一個賬號"
|
||||||
}
|
}
|
||||||
|
@@ -888,5 +888,12 @@
|
|||||||
},
|
},
|
||||||
"settingsHideBottomNav": "隱藏底部導航欄",
|
"settingsHideBottomNav": "隱藏底部導航欄",
|
||||||
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
"settingsHideBottomNavDescription": "隱藏底部導航欄,在側邊欄抽屜顯示導航按鈕。",
|
||||||
"reCaptcha": "人機驗證"
|
"reCaptcha": "人機驗證",
|
||||||
|
"friends": "好友",
|
||||||
|
"friendsDescription": "管理好友關係。",
|
||||||
|
"album": "相冊",
|
||||||
|
"albumDescription": "查看相冊與管理上傳附件。",
|
||||||
|
"stickers": "貼圖",
|
||||||
|
"stickersDescription": "查看貼圖包與管理貼圖。",
|
||||||
|
"navBottomUnauthorizedCaption": "或者註冊一個賬號"
|
||||||
}
|
}
|
||||||
|
176
lib/main.dart
176
lib/main.dart
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' hide log;
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
@@ -90,19 +89,14 @@ void main() async {
|
|||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
|
|
||||||
if (!kIsWeb && !Platform.isLinux) {
|
if (!kIsWeb && !Platform.isLinux) {
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||||
usePathUrlStrategy();
|
usePathUrlStrategy();
|
||||||
|
|
||||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
Workmanager().initialize(
|
Workmanager().initialize(appBackgroundDispatcher, isInDebugMode: kDebugMode);
|
||||||
appBackgroundDispatcher,
|
|
||||||
isInDebugMode: kDebugMode,
|
|
||||||
);
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
Workmanager().registerPeriodicTask(
|
Workmanager().registerPeriodicTask(
|
||||||
"widget-update-random-post",
|
"widget-update-random-post",
|
||||||
@@ -115,8 +109,7 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!kIsWeb && Platform.isAndroid) {
|
if (!kIsWeb && Platform.isAndroid) {
|
||||||
final ImagePickerPlatform imagePickerImplementation =
|
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
|
||||||
ImagePickerPlatform.instance;
|
|
||||||
if (imagePickerImplementation is ImagePickerAndroid) {
|
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||||
imagePickerImplementation.useAndroidPhotoPicker = true;
|
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||||
}
|
}
|
||||||
@@ -133,12 +126,7 @@ class SolianApp extends StatelessWidget {
|
|||||||
return ResponsiveBreakpoints.builder(
|
return ResponsiveBreakpoints.builder(
|
||||||
child: EasyLocalization(
|
child: EasyLocalization(
|
||||||
path: 'assets/translations',
|
path: 'assets/translations',
|
||||||
supportedLocales: [
|
supportedLocales: [Locale('en', 'US'), Locale('zh', 'CN'), Locale('zh', 'TW'), Locale('zh', 'HK')],
|
||||||
Locale('en', 'US'),
|
|
||||||
Locale('zh', 'CN'),
|
|
||||||
Locale('zh', 'TW'),
|
|
||||||
Locale('zh', 'HK'),
|
|
||||||
],
|
|
||||||
fallbackLocale: Locale('en', 'US'),
|
fallbackLocale: Locale('en', 'US'),
|
||||||
useFallbackTranslations: true,
|
useFallbackTranslations: true,
|
||||||
assetLoader: JsonAssetLoader(),
|
assetLoader: JsonAssetLoader(),
|
||||||
@@ -161,7 +149,7 @@ class SolianApp extends StatelessWidget {
|
|||||||
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
||||||
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnRealmProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => SnRealmProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
||||||
@@ -213,10 +201,7 @@ class _AppDelegate extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
routerConfig: appRouter,
|
routerConfig: appRouter,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return _AppSplashScreen(
|
return _AppSplashScreen(key: const Key('global-splash-screen'), child: child!);
|
||||||
key: const Key('global-splash-screen'),
|
|
||||||
child: child!,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -240,8 +225,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
if (prefs.containsKey('first_boot_time')) {
|
if (prefs.containsKey('first_boot_time')) {
|
||||||
final rawTime = prefs.getString('first_boot_time');
|
final rawTime = prefs.getString('first_boot_time');
|
||||||
final time = DateTime.tryParse(rawTime ?? '');
|
final time = DateTime.tryParse(rawTime ?? '');
|
||||||
if (time != null &&
|
if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
||||||
time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
|
||||||
final inAppReview = InAppReview.instance;
|
final inAppReview = InAppReview.instance;
|
||||||
if (prefs.getBool('rating_requested') == true) return;
|
if (prefs.getBool('rating_requested') == true) return;
|
||||||
if (await inAppReview.isAvailable()) {
|
if (await inAppReview.isAvailable()) {
|
||||||
@@ -262,30 +246,17 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
final info = await PackageInfo.fromPlatform();
|
final info = await PackageInfo.fromPlatform();
|
||||||
final localVersionString = '${info.version}+${info.buildNumber}';
|
final localVersionString = '${info.version}+${info.buildNumber}';
|
||||||
final resp = await Dio(
|
final resp = await Dio(
|
||||||
BaseOptions(
|
BaseOptions(sendTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60)),
|
||||||
sendTimeout: const Duration(seconds: 60),
|
).get('https://api.github.com/repos/Solsynth/HyperNet.Surface/releases/latest');
|
||||||
receiveTimeout: const Duration(seconds: 60),
|
|
||||||
),
|
|
||||||
).get(
|
|
||||||
'https://api.github.com/repos/Solsynth/HyperNet.Surface/releases/latest',
|
|
||||||
);
|
|
||||||
final remoteVersionString = resp.data?['tag_name'] ?? '0.0.0+0';
|
final remoteVersionString = resp.data?['tag_name'] ?? '0.0.0+0';
|
||||||
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
|
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
|
||||||
final localVersion = Version.parse(localVersionString.split('+').first);
|
final localVersion = Version.parse(localVersionString.split('+').first);
|
||||||
final remoteBuildNumber =
|
final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
||||||
int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0;
|
||||||
final localBuildNumber =
|
logging.info("[Update] Local: $localVersionString, Remote: $remoteVersionString");
|
||||||
int.tryParse(localVersionString.split('+').last) ?? 0;
|
if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) {
|
||||||
logging.info(
|
|
||||||
"[Update] Local: $localVersionString, Remote: $remoteVersionString");
|
|
||||||
if ((remoteVersion > localVersion ||
|
|
||||||
remoteBuildNumber > localBuildNumber) &&
|
|
||||||
mounted) {
|
|
||||||
final config = context.read<ConfigProvider>();
|
final config = context.read<ConfigProvider>();
|
||||||
config.setUpdate(
|
config.setUpdate(remoteVersionString, resp.data?['body'] ?? 'No changelog');
|
||||||
remoteVersionString,
|
|
||||||
resp.data?['body'] ?? 'No changelog',
|
|
||||||
);
|
|
||||||
logging.info("[Update] Update available: $remoteVersionString");
|
logging.info("[Update] Update available: $remoteVersionString");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -323,16 +294,21 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
_setPhaseText('websocket');
|
_setPhaseText('websocket');
|
||||||
final ws = context.read<WebSocketProvider>();
|
final ws = context.read<WebSocketProvider>();
|
||||||
await ws.tryConnect();
|
await ws.tryConnect();
|
||||||
if (!mounted) return;
|
try {
|
||||||
_setPhaseText('notification');
|
|
||||||
final notify = context.read<NotificationProvider>();
|
|
||||||
notify.listen();
|
|
||||||
await notify.registerPushNotifications();
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_setPhaseText('keyPair');
|
_setPhaseText('keyPair');
|
||||||
final kp = context.read<KeyPairProvider>();
|
final kp = context.read<KeyPairProvider>();
|
||||||
await kp.reloadActive();
|
await kp.reloadActive();
|
||||||
kp.listen();
|
kp.listen();
|
||||||
|
} catch (_) {}
|
||||||
|
if (ua.isAuthorized) {
|
||||||
|
if (!mounted) return;
|
||||||
|
_setPhaseText('notification');
|
||||||
|
final notify = context.read<NotificationProvider>();
|
||||||
|
notify.listen();
|
||||||
|
try {
|
||||||
|
await notify.registerPushNotifications();
|
||||||
|
} catch (_) {}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_setPhaseText('stickers');
|
_setPhaseText('stickers');
|
||||||
final sticker = context.read<SnStickerProvider>();
|
final sticker = context.read<SnStickerProvider>();
|
||||||
@@ -350,6 +326,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
final ct = context.read<ChatChannelProvider>();
|
final ct = context.read<ChatChannelProvider>();
|
||||||
await ct.refreshAvailableChannels();
|
await ct.refreshAvailableChannels();
|
||||||
_setPhaseText('done');
|
_setPhaseText('done');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await context.showErrorDialog(err);
|
await context.showErrorDialog(err);
|
||||||
@@ -367,35 +344,19 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
|
|
||||||
final Menu _appTrayMenu = Menu(
|
final Menu _appTrayMenu = Menu(
|
||||||
items: [
|
items: [
|
||||||
MenuItem(
|
MenuItem(key: 'version_label', label: 'Solian', disabled: true),
|
||||||
key: 'version_label',
|
|
||||||
label: 'Solian',
|
|
||||||
disabled: true,
|
|
||||||
),
|
|
||||||
MenuItem.separator(),
|
MenuItem.separator(),
|
||||||
MenuItem.checkbox(
|
MenuItem.checkbox(checked: false, key: 'mute_notification', label: 'trayMenuMuteNotification'.tr()),
|
||||||
checked: false,
|
|
||||||
key: 'mute_notification',
|
|
||||||
label: 'trayMenuMuteNotification'.tr(),
|
|
||||||
),
|
|
||||||
MenuItem.separator(),
|
MenuItem.separator(),
|
||||||
MenuItem(
|
MenuItem(key: 'window_show', label: 'trayMenuShow'.tr()),
|
||||||
key: 'window_show',
|
MenuItem(key: 'exit', label: 'trayMenuExit'.tr()),
|
||||||
label: 'trayMenuShow'.tr(),
|
|
||||||
),
|
|
||||||
MenuItem(
|
|
||||||
key: 'exit',
|
|
||||||
label: 'trayMenuExit'.tr(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> _trayInitialization() async {
|
Future<void> _trayInitialization() async {
|
||||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||||
|
|
||||||
final icon = Platform.isWindows
|
final icon = Platform.isWindows ? 'assets/icon/tray-icon.ico' : 'assets/icon/tray-icon.png';
|
||||||
? 'assets/icon/tray-icon.ico'
|
|
||||||
: 'assets/icon/tray-icon.png';
|
|
||||||
final appVersion = await PackageInfo.fromPlatform();
|
final appVersion = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
trayManager.addListener(this);
|
trayManager.addListener(this);
|
||||||
@@ -413,10 +374,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
Future<void> _notifyInitialization() async {
|
Future<void> _notifyInitialization() async {
|
||||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||||
|
|
||||||
await localNotifier.setup(
|
await localNotifier.setup(appName: 'Solian', shortcutPolicy: ShortcutPolicy.requireCreate);
|
||||||
appName: 'Solian',
|
|
||||||
shortcutPolicy: ShortcutPolicy.requireCreate,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppLifecycleListener? _appLifecycleListener;
|
AppLifecycleListener? _appLifecycleListener;
|
||||||
@@ -427,9 +385,7 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
|
|
||||||
_isBusy = true;
|
_isBusy = true;
|
||||||
if (!kIsWeb && !(Platform.isIOS || Platform.isAndroid)) {
|
if (!kIsWeb && !(Platform.isIOS || Platform.isAndroid)) {
|
||||||
_appLifecycleListener = AppLifecycleListener(
|
_appLifecycleListener = AppLifecycleListener(onExitRequested: _onExitRequested);
|
||||||
onExitRequested: _onExitRequested,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_trayInitialization();
|
_trayInitialization();
|
||||||
@@ -529,17 +485,26 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return SizeChangedLayoutNotifier(
|
return SizeChangedLayoutNotifier(
|
||||||
child: _isBusy
|
child:
|
||||||
|
_isBusy
|
||||||
? Material(
|
? Material(
|
||||||
key: Key('app-splash-screen-$_isBusy'),
|
key: Key('app-splash-screen-$_isBusy'),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
CustomPaint(painter: GraphPainter()),
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('assets/icon/kanban-1st.jpg'),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
opacity: 0.1,
|
||||||
|
),
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
backgroundBlendMode: BlendMode.darken,
|
||||||
|
),
|
||||||
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(maxWidth: 240),
|
||||||
maxWidth: 240,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -547,16 +512,12 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
'assets/icon/icon.png',
|
'assets/icon/icon.png',
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 64,
|
height: 64,
|
||||||
color:
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
),
|
||||||
Text('Solar Network').bold(),
|
Text('Solar Network').bold(),
|
||||||
AppVersionLabel(),
|
AppVersionLabel(),
|
||||||
Gap(8),
|
Gap(8),
|
||||||
Text(
|
Text(_phaseText, textAlign: TextAlign.center),
|
||||||
_phaseText,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
Gap(16),
|
Gap(16),
|
||||||
const LinearProgressIndicator(),
|
const LinearProgressIndicator(),
|
||||||
],
|
],
|
||||||
@@ -574,44 +535,3 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GraphPainter extends CustomPainter {
|
|
||||||
final Random random = Random();
|
|
||||||
final int numNodes = 20;
|
|
||||||
final double maxDistance = 100; // Max distance to draw a line
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paintNode = Paint()..color = Colors.white;
|
|
||||||
final paintEdge = Paint()
|
|
||||||
..color = Colors.white.withOpacity(0.3)
|
|
||||||
..strokeWidth = 1;
|
|
||||||
|
|
||||||
// Generate random points
|
|
||||||
List<Offset> nodes = List.generate(
|
|
||||||
numNodes,
|
|
||||||
(_) => Offset(
|
|
||||||
random.nextDouble() * size.width,
|
|
||||||
random.nextDouble() * size.height,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Draw edges between close nodes
|
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
|
||||||
for (var j = i + 1; j < nodes.length; j++) {
|
|
||||||
double distance = (nodes[i] - nodes[j]).distance;
|
|
||||||
if (distance < maxDistance) {
|
|
||||||
canvas.drawLine(nodes[i], nodes[j], paintEdge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw nodes
|
|
||||||
for (var node in nodes) {
|
|
||||||
canvas.drawCircle(node, 4, paintNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
@@ -41,6 +41,11 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addAvailableChannel(SnChannel channel) {
|
||||||
|
_availableChannels.add(channel);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
Future<void> _saveChannelToLocal(Iterable<SnChannel> channels) async {
|
||||||
await Future.wait(
|
await Future.wait(
|
||||||
channels.map(
|
channels.map(
|
||||||
|
@@ -61,26 +61,6 @@ class NavigationProvider extends ChangeNotifier {
|
|||||||
screen: 'news',
|
screen: 'news',
|
||||||
label: 'screenNews',
|
label: 'screenNews',
|
||||||
),
|
),
|
||||||
AppNavDestination(
|
|
||||||
icon: Icon(Symbols.emoji_emotions, weight: 400, opticalSize: 20),
|
|
||||||
screen: 'stickers',
|
|
||||||
label: 'screenStickers',
|
|
||||||
),
|
|
||||||
AppNavDestination(
|
|
||||||
icon: Icon(Symbols.photo_library, weight: 400, opticalSize: 20),
|
|
||||||
screen: 'album',
|
|
||||||
label: 'screenAlbum',
|
|
||||||
),
|
|
||||||
AppNavDestination(
|
|
||||||
icon: Icon(Symbols.diversity_4, weight: 400, opticalSize: 20),
|
|
||||||
screen: 'friend',
|
|
||||||
label: 'screenFriend',
|
|
||||||
),
|
|
||||||
AppNavDestination(
|
|
||||||
icon: Icon(Symbols.notifications, weight: 400, opticalSize: 20),
|
|
||||||
screen: 'notification',
|
|
||||||
label: 'screenNotification',
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
static const List<String> kDefaultPinnedDestination = [
|
static const List<String> kDefaultPinnedDestination = [
|
||||||
'home',
|
'home',
|
||||||
|
@@ -48,13 +48,11 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
var deviceUuid = await FlutterUdid.consistentUdid;
|
var deviceUuid = await FlutterUdid.consistentUdid;
|
||||||
|
|
||||||
if (deviceUuid.isEmpty) {
|
if (deviceUuid.isEmpty) {
|
||||||
logging.warning(
|
logging.warning('[Push Notification] Unable to active push notifications, couldn\'t get device uuid');
|
||||||
'[Push Notification] Unable to active push notifications, couldn\'t get device uuid');
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
logging.info('[Push Notification] Device UUID is $deviceUuid');
|
logging.info('[Push Notification] Device UUID is $deviceUuid');
|
||||||
logging
|
logging.info('[Push Notification] Registering device push notifications...');
|
||||||
.info('[Push Notification] Registering device push notifications...');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isIOS || Platform.isMacOS) {
|
if (Platform.isIOS || Platform.isMacOS) {
|
||||||
@@ -66,14 +64,14 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
logging.info('[Push Notification] Device Push Token is $token');
|
logging.info('[Push Notification] Device Push Token is $token');
|
||||||
|
|
||||||
|
try {
|
||||||
await _sn.client.post(
|
await _sn.client.post(
|
||||||
'/cgi/id/notifications/subscription',
|
'/cgi/id/notifications/subscription',
|
||||||
data: {
|
data: {'provider': provider, 'device_token': token, 'device_id': deviceUuid},
|
||||||
'provider': provider,
|
|
||||||
'device_token': token,
|
|
||||||
'device_id': deviceUuid,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
} catch (err) {
|
||||||
|
logging.error('[Push Notification] Unable to register push notifications: $err');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int showingCount = 0;
|
int showingCount = 0;
|
||||||
@@ -91,8 +89,7 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
final doHaptic = _cfg.prefs.getBool(kAppNotifyWithHaptic) ?? true;
|
||||||
if (doHaptic) HapticFeedback.mediumImpact();
|
if (doHaptic) HapticFeedback.mediumImpact();
|
||||||
|
|
||||||
if (notification.topic == 'messaging.message' &&
|
if (notification.topic == 'messaging.message' && skippableNotifyChannel != null) {
|
||||||
skippableNotifyChannel != null) {
|
|
||||||
if (notification.metadata['channel_id'] != null &&
|
if (notification.metadata['channel_id'] != null &&
|
||||||
notification.metadata['channel_id'] == skippableNotifyChannel) {
|
notification.metadata['channel_id'] == skippableNotifyChannel) {
|
||||||
return;
|
return;
|
||||||
|
@@ -8,7 +8,7 @@ import 'package:surface/providers/database.dart';
|
|||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
|
|
||||||
class SnRealmProvider {
|
class SnRealmProvider extends ChangeNotifier {
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final DatabaseProvider _dt;
|
late final DatabaseProvider _dt;
|
||||||
|
|
||||||
@@ -39,6 +39,11 @@ class SnRealmProvider {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addAvailableRealm(SnRealm realm) {
|
||||||
|
_availableRealms.add(realm);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Future<SnRealm> getRealm(dynamic aliasOrId) async {
|
Future<SnRealm> getRealm(dynamic aliasOrId) async {
|
||||||
if (_cache.containsKey(aliasOrId.toString())) {
|
if (_cache.containsKey(aliasOrId.toString())) {
|
||||||
return _cache[aliasOrId.toString()]!;
|
return _cache[aliasOrId.toString()]!;
|
||||||
|
@@ -4,8 +4,7 @@ import 'package:crypto/crypto.dart';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:surface/logger.dart';
|
import 'package:surface/logger.dart';
|
||||||
|
|
||||||
// TODO self host translate api
|
const kTranslateApiBaseUrl = 'https://translate.solsynth.dev';
|
||||||
const kTranslateApiBaseUrl = 'https://translate.disroot.org';
|
|
||||||
|
|
||||||
class SnTranslator {
|
class SnTranslator {
|
||||||
final Dio client = Dio(
|
final Dio client = Dio(
|
||||||
|
@@ -64,6 +64,7 @@ class UserProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<SnAccount?> refreshUser() async {
|
Future<SnAccount?> refreshUser() async {
|
||||||
|
if (!isAuthorized) return null;
|
||||||
final resp = await _sn.client.get('/cgi/id/users/me');
|
final resp = await _sn.client.get('/cgi/id/users/me');
|
||||||
final out = SnAccount.fromJson(resp.data);
|
final out = SnAccount.fromJson(resp.data);
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ import 'package:surface/screens/account/prefs/notify.dart';
|
|||||||
import 'package:surface/screens/account/prefs/security.dart';
|
import 'package:surface/screens/account/prefs/security.dart';
|
||||||
import 'package:surface/screens/account/profile_page.dart';
|
import 'package:surface/screens/account/profile_page.dart';
|
||||||
import 'package:surface/screens/account/profile_edit.dart';
|
import 'package:surface/screens/account/profile_edit.dart';
|
||||||
|
import 'package:surface/screens/account/programs.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';
|
||||||
@@ -130,6 +131,11 @@ final _appRoutes = [
|
|||||||
name: 'account',
|
name: 'account',
|
||||||
builder: (context, state) => const AccountScreen(),
|
builder: (context, state) => const AccountScreen(),
|
||||||
routes: [
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/programs',
|
||||||
|
name: 'accountProgram',
|
||||||
|
builder: (context, state) => const AccountProgramScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/contacts',
|
path: '/contacts',
|
||||||
name: 'accountContactMethods',
|
name: 'accountContactMethods',
|
||||||
|
@@ -30,19 +30,7 @@ class AccountScreen extends StatelessWidget {
|
|||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text(
|
title: Text("screenAccount").tr(),
|
||||||
"screenAccount",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
shadows: [
|
|
||||||
Shadow(
|
|
||||||
offset: Offset(1, 1),
|
|
||||||
blurRadius: 5.0,
|
|
||||||
color: Color.fromARGB(255, 0, 0, 0),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
flexibleSpace: ua.user != null && ua.user!.banner.isNotEmpty
|
flexibleSpace: ua.user != null && ua.user!.banner.isNotEmpty
|
||||||
? Stack(
|
? Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
@@ -158,23 +146,43 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('abuseReport').tr(),
|
title: Text('accountProgram').tr(),
|
||||||
subtitle: Text('abuseReportActionDescription').tr(),
|
subtitle: Text('accountProgramDescription').tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.flag),
|
leading: const Icon(Symbols.communities),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed('abuseReport');
|
GoRouter.of(context).pushNamed('accountProgram');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('factorSettings').tr(),
|
title: Text('friends').tr(),
|
||||||
subtitle: Text('factorSettingsSubtitle').tr(),
|
subtitle: Text('friendsDescription').tr(),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.lock),
|
leading: const Icon(Symbols.person),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed('factorSettings');
|
GoRouter.of(context).pushNamed('friend');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('album').tr(),
|
||||||
|
subtitle: Text('albumDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.photo_library),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('album');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('stickers').tr(),
|
||||||
|
subtitle: Text('stickersDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.emoji_emotions),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('stickers');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -237,6 +245,16 @@ class _AuthorizedAccountScreen extends StatelessWidget {
|
|||||||
GoRouter.of(context).pushNamed('accountSettings');
|
GoRouter.of(context).pushNamed('accountSettings');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('abuseReport').tr(),
|
||||||
|
subtitle: Text('abuseReportActionDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.flag),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('abuseReport');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountLogout').tr(),
|
title: Text('accountLogout').tr(),
|
||||||
subtitle: Text('accountLogoutSubtitle').tr(),
|
subtitle: Text('accountLogoutSubtitle').tr(),
|
||||||
@@ -298,9 +316,7 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
GoRouter.of(context).pushNamed('authLogin').then((value) {
|
GoRouter.of(context).pushNamed('authLogin').then((value) {
|
||||||
if (value == true && context.mounted) {
|
if (value == true && context.mounted) {
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
context.showSnackbar('loginSuccess'.tr(args: [
|
ua.refreshUser();
|
||||||
'@${ua.user?.name} (${ua.user?.nick})',
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@@ -117,6 +117,16 @@ class AccountSettingsScreen extends StatelessWidget {
|
|||||||
GoRouter.of(context).pushNamed('accountSettingsSecurity');
|
GoRouter.of(context).pushNamed('accountSettingsSecurity');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('factorSettings').tr(),
|
||||||
|
subtitle: Text('factorSettingsSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.lock),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('factorSettings');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('accountProfileEdit').tr(),
|
title: Text('accountProfileEdit').tr(),
|
||||||
subtitle: Text('accountProfileEditSubtitle').tr(),
|
subtitle: Text('accountProfileEditSubtitle').tr(),
|
||||||
|
284
lib/screens/account/programs.dart
Normal file
284
lib/screens/account/programs.dart
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.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:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/experience.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/account.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class AccountProgramScreen extends StatefulWidget {
|
||||||
|
const AccountProgramScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccountProgramScreen> createState() => _AccountProgramScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountProgramScreenState extends State<AccountProgramScreen> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
final List<SnProgram> _programs = List.empty(growable: true);
|
||||||
|
final List<SnProgramMember> _programMembers = List.empty(growable: true);
|
||||||
|
|
||||||
|
Future<void> _fetchPrograms() async {
|
||||||
|
_programs.clear();
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/programs');
|
||||||
|
_programs.addAll(
|
||||||
|
resp.data.map((ele) => SnProgram.fromJson(ele)).cast<SnProgram>(),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchProgramMembers() async {
|
||||||
|
_programMembers.clear();
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/programs/members');
|
||||||
|
_programMembers.addAll(
|
||||||
|
resp.data
|
||||||
|
.map((ele) => SnProgramMember.fromJson(ele))
|
||||||
|
.cast<SnProgramMember>(),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchPrograms();
|
||||||
|
_fetchProgramMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('accountProgram').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: _programs.length,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final ele = _programs[idx];
|
||||||
|
return Card(
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _ProgramJoinPopup(
|
||||||
|
program: ele,
|
||||||
|
isJoined: _programMembers
|
||||||
|
.any((ele) => ele.programId == ele.id),
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
_fetchProgramMembers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (ele.appearance['banner'] != null)
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 5,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceVariant,
|
||||||
|
child: Image.network(
|
||||||
|
ele.appearance['banner'],
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
ele.name,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium,
|
||||||
|
).bold(),
|
||||||
|
Text(
|
||||||
|
ele.description,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
if (_programMembers
|
||||||
|
.any((ele) => ele.programId == ele.id))
|
||||||
|
Text('accountProgramAlreadyJoined'.tr())
|
||||||
|
.opacity(0.75),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).padding(horizontal: 8);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProgramJoinPopup extends StatefulWidget {
|
||||||
|
final SnProgram program;
|
||||||
|
final bool isJoined;
|
||||||
|
const _ProgramJoinPopup({required this.program, required this.isJoined});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_ProgramJoinPopup> createState() => _ProgramJoinPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProgramJoinPopupState extends State<_ProgramJoinPopup> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _joinProgram() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/id/programs/${widget.program.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
context.showSnackbar('accountProgramJoined'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _leaveProgram() async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.delete('/cgi/id/programs/${widget.program.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
context.showSnackbar('accountProgramLeft'.tr());
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.add, size: 24),
|
||||||
|
const Gap(16),
|
||||||
|
Text(
|
||||||
|
'accountProgramJoin',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (widget.program.appearance['banner'] != null)
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 5,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
|
child: Image.network(
|
||||||
|
widget.program.appearance['banner'],
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).padding(bottom: 12),
|
||||||
|
Text(
|
||||||
|
widget.program.name,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
).bold(),
|
||||||
|
Text(
|
||||||
|
widget.program.description,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'accountProgramJoinRequirements',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
).tr().bold(),
|
||||||
|
Text('≥EXP ${widget.program.expRequirement}'),
|
||||||
|
Text('≥Lv${getLevelFromExp(widget.program.expRequirement)}'),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
'accountProgramJoinPricing',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
).tr().bold(),
|
||||||
|
Text('walletCurrency${widget.program.price['currency'].toString().capitalize().replaceFirst('Normal', '')}')
|
||||||
|
.plural(widget.program.price['amount'].toDouble()),
|
||||||
|
Text('accountProgramJoinPricingHint').tr().opacity(0.75),
|
||||||
|
const Gap(8),
|
||||||
|
if (widget.isJoined)
|
||||||
|
Text('accountProgramLeaveHint')
|
||||||
|
.tr()
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(bottom: 8),
|
||||||
|
if (!widget.isJoined)
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _isBusy ? null : _joinProgram,
|
||||||
|
child: Text('join').tr(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _isBusy ? null : _leaveProgram,
|
||||||
|
child: Text('leave').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -10,7 +10,6 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
|
||||||
import 'package:surface/widgets/attachment/attachment_zoom.dart';
|
import 'package:surface/widgets/attachment/attachment_zoom.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
@@ -106,7 +105,7 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: PageBackButton(),
|
||||||
title: Text('screenAlbum').tr(),
|
title: Text('screenAlbum').tr(),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
@@ -119,7 +118,8 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
value: _billing?.includedRatio ?? 0,
|
value: _billing?.includedRatio ?? 0,
|
||||||
strokeWidth: 8,
|
strokeWidth: 8,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
).padding(all: 12),
|
).padding(all: 12),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
@@ -129,7 +129,8 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text('attachmentBillingUploaded').tr().bold(),
|
Text('attachmentBillingUploaded').tr().bold(),
|
||||||
Text(
|
Text(
|
||||||
(_billing?.currentBytes ?? 0).formatBytes(decimals: 4),
|
(_billing?.currentBytes ?? 0)
|
||||||
|
.formatBytes(decimals: 4),
|
||||||
style: GoogleFonts.robotoMono(),
|
style: GoogleFonts.robotoMono(),
|
||||||
),
|
),
|
||||||
Text('attachmentBillingDiscount').tr().bold(),
|
Text('attachmentBillingDiscount').tr().bold(),
|
||||||
|
@@ -7,7 +7,7 @@ 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:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/screens/captcha.dart';
|
import 'package:surface/screens/captcha/captcha.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
@@ -43,7 +43,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
|||||||
|
|
||||||
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => TurnstileScreen(),
|
builder: (context) => CaptchaScreen(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (captchaTk == null) return;
|
if (captchaTk == null) return;
|
||||||
|
3
lib/screens/captcha/captcha.dart
Normal file
3
lib/screens/captcha/captcha.dart
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
|
||||||
|
export 'captcha_native.dart' if (kIsWeb) 'captcha_web.dart';
|
@@ -5,19 +5,18 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
class TurnstileScreen extends StatefulWidget {
|
class CaptchaScreen extends StatefulWidget {
|
||||||
const TurnstileScreen({
|
const CaptchaScreen({super.key});
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TurnstileScreen> createState() => _TurnstileScreenState();
|
State<CaptchaScreen> createState() => _CaptchaScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TurnstileScreenState extends State<TurnstileScreen> {
|
class _CaptchaScreenState extends State<CaptchaScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.read<ConfigProvider>();
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(title: Text("reCaptcha").tr()),
|
appBar: AppBar(title: Text("reCaptcha").tr()),
|
||||||
body: InAppWebView(
|
body: InAppWebView(
|
54
lib/screens/captcha/captcha_web.dart
Normal file
54
lib/screens/captcha/captcha_web.dart
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import 'dart:html' as html;
|
||||||
|
import 'dart:ui_web' as ui;
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
|
||||||
|
class CaptchaScreen extends StatefulWidget {
|
||||||
|
const CaptchaScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CaptchaScreen> createState() => _CaptchaScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CaptchaScreenState extends State<CaptchaScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_setupWebListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupWebListener() {
|
||||||
|
html.window.onMessage.listen((event) {
|
||||||
|
if (event.data != null && event.data is String) {
|
||||||
|
final message = event.data as String;
|
||||||
|
if (message.startsWith("captcha_tk=")) {
|
||||||
|
String token = message.replaceFirst("captcha_tk=", "");
|
||||||
|
Navigator.pop(context, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final iframe = html.IFrameElement()
|
||||||
|
..src = '${context.read<ConfigProvider>().serverUrl}/captcha?redirect_uri=web'
|
||||||
|
..style.border = 'none'
|
||||||
|
..width = '100%'
|
||||||
|
..height = '100%';
|
||||||
|
|
||||||
|
html.document.body!.append(iframe);
|
||||||
|
ui.platformViewRegistry.registerViewFactory(
|
||||||
|
'captcha-iframe',
|
||||||
|
(int viewId) => iframe,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text("reCaptcha").tr()),
|
||||||
|
body: HtmlElementView(viewType: 'captcha-iframe'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -46,9 +46,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/users/me/relations?status=1');
|
final resp = await sn.client.get('/cgi/id/users/me/relations?status=1');
|
||||||
_relations = List<SnRelationship>.from(
|
_relations = List<SnRelationship>.from(resp.data?.map((e) => SnRelationship.fromJson(e)) ?? []);
|
||||||
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -66,9 +64,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/users/me/relations?status=0,3');
|
final resp = await sn.client.get('/cgi/id/users/me/relations?status=0,3');
|
||||||
_requests = List<SnRelationship>.from(
|
_requests = List<SnRelationship>.from(resp.data?.map((e) => SnRelationship.fromJson(e)) ?? []);
|
||||||
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -86,9 +82,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/users/me/relations?status=2');
|
final resp = await sn.client.get('/cgi/id/users/me/relations?status=2');
|
||||||
_blocks = List<SnRelationship>.from(
|
_blocks = List<SnRelationship>.from(resp.data?.map((e) => SnRelationship.fromJson(e)) ?? []);
|
||||||
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -104,11 +98,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final rel = context.read<SnRelationshipProvider>();
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
await rel.updateRelationship(
|
await rel.updateRelationship(relation.relatedId, dstStatus, relation.permNodes);
|
||||||
relation.relatedId,
|
|
||||||
dstStatus,
|
|
||||||
relation.permNodes,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_fetchRelations();
|
_fetchRelations();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -122,9 +112,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
Future<void> _deleteRelation(SnRelationship relation) async {
|
Future<void> _deleteRelation(SnRelationship relation) async {
|
||||||
final confirm = await context.showConfirmDialog(
|
final confirm = await context.showConfirmDialog(
|
||||||
'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
|
'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
|
||||||
'friendDeleteDescription'.tr(args: [
|
'friendDeleteDescription'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
|
||||||
relation.related?.nick ?? 'unknown'.tr(),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
if (!confirm) return;
|
if (!confirm) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -145,10 +133,9 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showRequests() {
|
void _showRequests() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(context: context, builder: (context) => _FriendshipListWidget(relations: _requests)).then((
|
||||||
context: context,
|
value,
|
||||||
builder: (context) => _FriendshipListWidget(relations: _requests),
|
) {
|
||||||
).then((value) {
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
_fetchRequests();
|
_fetchRequests();
|
||||||
_fetchRelations();
|
_fetchRelations();
|
||||||
@@ -157,10 +144,9 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showBlocks() {
|
void _showBlocks() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(context: context, builder: (context) => _FriendshipListWidget(relations: _blocks)).then((
|
||||||
context: context,
|
value,
|
||||||
builder: (context) => _FriendshipListWidget(relations: _blocks),
|
) {
|
||||||
).then((value) {
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
_fetchBlocks();
|
_fetchBlocks();
|
||||||
_fetchRelations();
|
_fetchRelations();
|
||||||
@@ -173,9 +159,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.post('/cgi/id/users/me/relations', data: {
|
await sn.client.post('/cgi/id/users/me/relations', data: {'related': user.name});
|
||||||
'related': user.name,
|
|
||||||
});
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar('friendRequestSent'.tr());
|
context.showSnackbar('friendRequestSent'.tr());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -200,29 +184,19 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
|
|
||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(leading: PageBackButton(), title: Text('screenFriend').tr()),
|
||||||
leading: AutoAppBarLeading(),
|
body: Center(child: UnauthorizedHint()),
|
||||||
title: Text('screenFriend').tr(),
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
child: UnauthorizedHint(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(leading: AutoAppBarLeading(), title: Text('screenFriend').tr()),
|
||||||
leading: AutoAppBarLeading(),
|
|
||||||
title: Text('screenFriend').tr(),
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Symbols.add),
|
child: const Icon(Symbols.add),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final user = await showModalBottomSheet<SnAccount?>(
|
final user = await showModalBottomSheet<SnAccount?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AccountSelect(
|
builder: (context) => AccountSelect(title: 'friendNew'.tr()),
|
||||||
title: 'friendNew'.tr(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
@@ -235,9 +209,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
if (_requests.isNotEmpty)
|
if (_requests.isNotEmpty)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('friendRequests').tr(),
|
title: Text('friendRequests').tr(),
|
||||||
subtitle: Text(
|
subtitle: Text('friendRequestsDescription').plural(_requests.length),
|
||||||
'friendRequestsDescription',
|
|
||||||
).plural(_requests.length),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.group_add),
|
leading: const Icon(Symbols.group_add),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@@ -246,9 +218,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
if (_blocks.isNotEmpty)
|
if (_blocks.isNotEmpty)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('friendBlocklist').tr(),
|
title: Text('friendBlocklist').tr(),
|
||||||
subtitle: Text(
|
subtitle: Text('friendBlocklistDescription').plural(_blocks.length),
|
||||||
'friendBlocklistDescription',
|
|
||||||
).plural(_blocks.length),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: const Icon(Symbols.block),
|
leading: const Icon(Symbols.block),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@@ -260,17 +230,15 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
context: context,
|
context: context,
|
||||||
removeTop: true,
|
removeTop: true,
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () => Future.wait([
|
onRefresh: () => Future.wait([_fetchRelations(), _fetchRequests()]),
|
||||||
_fetchRelations(),
|
|
||||||
_fetchRequests(),
|
|
||||||
]),
|
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: _relations.length,
|
itemCount: _relations.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final relation = _relations[index];
|
final relation = _relations[index];
|
||||||
final other = relation.related;
|
final other = relation.related;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding: const EdgeInsets.only(right: 24, left: 16),
|
contentPadding:
|
||||||
|
const EdgeInsets.only(right: 24, left: 16),
|
||||||
leading: AccountImage(content: other?.avatar),
|
leading: AccountImage(content: other?.avatar),
|
||||||
title: Text(other?.nick ?? 'unknown'),
|
title: Text(other?.nick ?? 'unknown'),
|
||||||
subtitle: Text(other?.nick ?? 'unknown'),
|
subtitle: Text(other?.nick ?? 'unknown'),
|
||||||
@@ -360,11 +328,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final rel = context.read<SnRelationshipProvider>();
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
await rel.updateRelationship(
|
await rel.updateRelationship(relation.relatedId, dstStatus, relation.permNodes);
|
||||||
relation.relatedId,
|
|
||||||
dstStatus,
|
|
||||||
relation.permNodes,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -378,9 +342,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
|||||||
Future<void> _deleteRelation(SnRelationship relation) async {
|
Future<void> _deleteRelation(SnRelationship relation) async {
|
||||||
final confirm = await context.showConfirmDialog(
|
final confirm = await context.showConfirmDialog(
|
||||||
'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
|
'friendDelete'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
|
||||||
'friendDeleteDescription'.tr(args: [
|
'friendDeleteDescription'.tr(args: [relation.related?.nick ?? 'unknown'.tr()]),
|
||||||
relation.related?.nick ?? 'unknown'.tr(),
|
|
||||||
]),
|
|
||||||
);
|
);
|
||||||
if (!confirm) return;
|
if (!confirm) return;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
@@ -18,7 +18,7 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/special_day.dart';
|
import 'package:surface/providers/special_day.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/providers/widget.dart';
|
import 'package:surface/providers/widget.dart';
|
||||||
import 'package:surface/screens/captcha.dart';
|
import 'package:surface/screens/captcha/captcha.dart';
|
||||||
import 'package:surface/types/check_in.dart';
|
import 'package:surface/types/check_in.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
@@ -511,7 +511,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
Future<void> _doCheckIn() async {
|
Future<void> _doCheckIn() async {
|
||||||
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
final captchaTk = await Navigator.of(context, rootNavigator: true).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => TurnstileScreen(),
|
builder: (context) => CaptchaScreen(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (captchaTk == null) return;
|
if (captchaTk == null) return;
|
||||||
@@ -806,7 +806,7 @@ class _HomeDashNotificationWidgetState
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Symbols.arrow_right_alt),
|
icon: const Icon(Symbols.arrow_right_alt),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
GoRouter.of(context).goNamed('notification');
|
GoRouter.of(context).pushNamed('notification');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -149,8 +149,9 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
if (!ua.isAuthorized) {
|
if (!ua.isAuthorized) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: PageBackButton(),
|
||||||
title: Text('screenNotification').tr()),
|
title: Text('screenNotification').tr(),
|
||||||
|
),
|
||||||
body: Center(child: UnauthorizedHint()),
|
body: Center(child: UnauthorizedHint()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -28,11 +28,8 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
final result = await pt.listPosts(
|
final result =
|
||||||
take: 10,
|
await pt.listPosts(take: 10, offset: _posts.length, isShuffle: true);
|
||||||
offset: _posts.length,
|
|
||||||
isShuffle: true,
|
|
||||||
);
|
|
||||||
_posts.addAll(result.$1);
|
_posts.addAll(result.$1);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@@ -57,19 +54,14 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text('postShuffle').tr()),
|
||||||
title: Text('postShuffle').tr(),
|
|
||||||
),
|
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
if (_isBusy || _posts.isEmpty)
|
if (_isBusy || _posts.isEmpty)
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: Center(
|
child: Center(child: CircularProgressIndicator()))
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CardSwiper(
|
child: CardSwiper(
|
||||||
@@ -81,6 +73,8 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
|
|||||||
final ele = _posts[idx];
|
final ele = _posts[idx];
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
|
child: Card(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: OpenablePostItem(
|
child: OpenablePostItem(
|
||||||
key: ValueKey(ele),
|
key: ValueKey(ele),
|
||||||
data: ele,
|
data: ele,
|
||||||
@@ -92,6 +86,7 @@ class _PostShuffleScreenState extends State<PostShuffleScreen> {
|
|||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
},
|
},
|
||||||
|
).padding(all: 8),
|
||||||
).padding(
|
).padding(
|
||||||
all: 24,
|
all: 24,
|
||||||
bottom:
|
bottom:
|
||||||
|
@@ -4,8 +4,10 @@ import 'package:gap/gap.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:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/channel.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/sn_realm.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
@@ -57,7 +59,9 @@ class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
|||||||
title: Text('screenRealmDiscovery').tr(),
|
title: Text('screenRealmDiscovery').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: _isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
icon: _isCompactView
|
||||||
|
? const Icon(Symbols.view_list)
|
||||||
|
: const Icon(Symbols.view_module),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() => _isCompactView = !_isCompactView);
|
setState(() => _isCompactView = !_isCompactView);
|
||||||
context.read<ConfigProvider>().realmCompactView = _isCompactView;
|
context.read<ConfigProvider>().realmCompactView = _isCompactView;
|
||||||
@@ -117,7 +121,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
|||||||
try {
|
try {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/im/channels/${widget.realm.alias}/public');
|
final resp =
|
||||||
|
await sn.client.get('/cgi/im/channels/${widget.realm.alias}/public');
|
||||||
final out = List<SnChannel>.from(
|
final out = List<SnChannel>.from(
|
||||||
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
||||||
);
|
);
|
||||||
@@ -135,10 +140,13 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
|||||||
setState(() => _isJoining = true);
|
setState(() => _isJoining = true);
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
await sn.client.post('/cgi/id/realms/${widget.realm.alias}/members', data: {
|
final rel = context.read<SnRealmProvider>();
|
||||||
|
await sn.client
|
||||||
|
.post('/cgi/id/realms/${widget.realm.alias}/members', data: {
|
||||||
'related': ua.user?.name,
|
'related': ua.user?.name,
|
||||||
});
|
});
|
||||||
await _joinSelectedChannels();
|
await _joinSelectedChannels();
|
||||||
|
rel.addAvailableRealm(widget.realm);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar('realmJoined'.tr(args: [widget.realm.name]));
|
context.showSnackbar('realmJoined'.tr(args: [widget.realm.name]));
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -156,13 +164,20 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
|||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
await sn.client.post('/cgi/im/channels/${widget.realm.alias}/$channel/members', data: {
|
await sn.client.post(
|
||||||
|
'/cgi/im/channels/${widget.realm.alias}/$channel/members',
|
||||||
|
data: {
|
||||||
'related': ua.user?.name,
|
'related': ua.user?.name,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
|
final ct = context.read<ChatChannelProvider>();
|
||||||
|
for (final channel
|
||||||
|
in _channels!.where((ele) => _planJoinChannels.contains(ele.alias))) {
|
||||||
|
ct.addAvailableChannel(channel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +197,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.group_add, size: 24),
|
const Icon(Symbols.group_add, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('realmJoin', style: Theme.of(context).textTheme.titleLarge).tr(),
|
Text('realmJoin', style: Theme.of(context).textTheme.titleLarge)
|
||||||
|
.tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Row(
|
Row(
|
||||||
@@ -216,7 +232,8 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
|||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child: Text('realmCommunityPublicChannelsHint'.tr(), style: Theme.of(context).textTheme.bodyMedium)
|
child: Text('realmCommunityPublicChannelsHint'.tr(),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium)
|
||||||
.padding(horizontal: 24, vertical: 8),
|
.padding(horizontal: 24, vertical: 8),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@@ -9,7 +9,6 @@ import 'package:surface/providers/sn_network.dart';
|
|||||||
import 'package:surface/providers/sn_sticker.dart';
|
import 'package:surface/providers/sn_sticker.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
|
||||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
import 'package:surface/widgets/attachment/attachment_item.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';
|
||||||
@@ -134,7 +133,7 @@ class _StickerScreenState extends State<StickerScreen>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: AutoAppBarLeading(),
|
leading: PageBackButton(),
|
||||||
title: Text('screenStickers').tr(),
|
title: Text('screenStickers').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@@ -45,10 +45,7 @@ class _WalletScreenState extends State<WalletScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(leading: PageBackButton(), title: Text('screenAccountWallet').tr()),
|
||||||
leading: PageBackButton(),
|
|
||||||
title: Text('screenAccountWallet').tr(),
|
|
||||||
),
|
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
LoadingIndicator(isActive: _isBusy),
|
LoadingIndicator(isActive: _isBusy),
|
||||||
@@ -66,11 +63,6 @@ class _WalletScreenState extends State<WalletScreen> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
|
||||||
radius: 28,
|
|
||||||
child: Icon(Symbols.wallet, size: 28),
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
SizedBox(width: double.infinity),
|
SizedBox(width: double.infinity),
|
||||||
Text(
|
Text(
|
||||||
NumberFormat.compactCurrency(
|
NumberFormat.compactCurrency(
|
||||||
@@ -81,6 +73,16 @@ class _WalletScreenState extends State<WalletScreen> {
|
|||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
Text('walletCurrency'.plural(double.parse(_wallet!.balance))),
|
Text('walletCurrency'.plural(double.parse(_wallet!.balance))),
|
||||||
|
const Gap(16),
|
||||||
|
Text(
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: EasyLocalization.of(context)!.currentLocale.toString(),
|
||||||
|
symbol: '${'walletCurrencyGoldenShort'.tr()} ',
|
||||||
|
decimalDigits: 2,
|
||||||
|
).format(double.parse(_wallet!.goldenBalance)),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
Text('walletCurrencyGolden'.plural(double.parse(_wallet!.goldenBalance))),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, vertical: 24),
|
).padding(horizontal: 20, vertical: 24),
|
||||||
).padding(horizontal: 8, top: 16, bottom: 4),
|
).padding(horizontal: 8, top: 16, bottom: 4),
|
||||||
@@ -109,14 +111,12 @@ class _WalletTransactionListState extends State<_WalletTransactionList> {
|
|||||||
try {
|
try {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/wa/transactions/me', queryParameters: {
|
final resp = await sn.client.get(
|
||||||
'take': 10,
|
'/cgi/wa/transactions/me',
|
||||||
'offset': _transactions.length,
|
queryParameters: {'take': 10, 'offset': _transactions.length},
|
||||||
});
|
|
||||||
_totalCount = resp.data['count'];
|
|
||||||
_transactions.addAll(
|
|
||||||
resp.data['data']?.map((e) => SnTransaction.fromJson(e)).cast<SnTransaction>() ?? [],
|
|
||||||
);
|
);
|
||||||
|
_totalCount = resp.data['count'];
|
||||||
|
_transactions.addAll(resp.data['data']?.map((e) => SnTransaction.fromJson(e)).cast<SnTransaction>() ?? []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -159,13 +159,19 @@ class _WalletTransactionListState extends State<_WalletTransactionList> {
|
|||||||
children: [
|
children: [
|
||||||
Text(ele.remark),
|
Text(ele.remark),
|
||||||
const Gap(2),
|
const Gap(2),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
DateFormat(
|
'walletTransactionType${ele.currency.capitalize()}'.tr(),
|
||||||
null,
|
|
||||||
EasyLocalization.of(context)!.currentLocale.toString(),
|
|
||||||
).format(ele.createdAt),
|
|
||||||
style: Theme.of(context).textTheme.labelSmall,
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
|
Text(' · ').textStyle(Theme.of(context).textTheme.labelSmall!).padding(right: 4),
|
||||||
|
Text(
|
||||||
|
DateFormat(null, EasyLocalization.of(context)!.currentLocale.toString()).format(ele.createdAt),
|
||||||
|
style: Theme.of(context).textTheme.labelSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
@@ -193,7 +199,8 @@ class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
|
|||||||
final TextEditingController passwordController = TextEditingController();
|
final TextEditingController passwordController = TextEditingController();
|
||||||
final password = await showDialog<String?>(
|
final password = await showDialog<String?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder:
|
||||||
|
(ctx) => AlertDialog(
|
||||||
title: Text('walletCreate').tr(),
|
title: Text('walletCreate').tr(),
|
||||||
content: Column(
|
content: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -205,17 +212,12 @@ class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(labelText: 'fieldPassword'.tr()),
|
||||||
labelText: 'fieldPassword'.tr(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text('cancel').tr()),
|
||||||
onPressed: () => Navigator.of(ctx).pop(),
|
|
||||||
child: Text('cancel').tr(),
|
|
||||||
),
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(ctx).pop(passwordController.text);
|
Navigator.of(ctx).pop(passwordController.text);
|
||||||
@@ -234,9 +236,7 @@ class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
|
|||||||
try {
|
try {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client.post('/cgi/wa/wallets/me', data: {
|
await sn.client.post('/cgi/wa/wallets/me', data: {'password': password});
|
||||||
'password': password,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -255,20 +255,14 @@ class _CreateWalletWidgetState extends State<_CreateWalletWidget> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(radius: 28, child: Icon(Symbols.add, size: 28)),
|
||||||
radius: 28,
|
|
||||||
child: Icon(Symbols.add, size: 28),
|
|
||||||
),
|
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Text('walletCreate', style: Theme.of(context).textTheme.titleLarge).tr(),
|
Text('walletCreate', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||||
Text('walletCreateSubtitle', style: Theme.of(context).textTheme.bodyMedium).tr(),
|
Text('walletCreateSubtitle', style: Theme.of(context).textTheme.bodyMedium).tr(),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton(
|
child: TextButton(onPressed: _isBusy ? null : () => _createWallet(), child: Text('next').tr()),
|
||||||
onPressed: _isBusy ? null : () => _createWallet(),
|
|
||||||
child: Text('next').tr(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, vertical: 24),
|
).padding(horizontal: 20, vertical: 24),
|
||||||
|
@@ -184,3 +184,42 @@ abstract class SnActionEvent with _$SnActionEvent {
|
|||||||
factory SnActionEvent.fromJson(Map<String, Object?> json) =>
|
factory SnActionEvent.fromJson(Map<String, Object?> json) =>
|
||||||
_$SnActionEventFromJson(json);
|
_$SnActionEventFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SnProgram with _$SnProgram {
|
||||||
|
const factory SnProgram({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
required String name,
|
||||||
|
required String description,
|
||||||
|
required String alias,
|
||||||
|
required int expRequirement,
|
||||||
|
required Map<String, dynamic> price,
|
||||||
|
required Map<String, dynamic> badge,
|
||||||
|
required Map<String, dynamic> group,
|
||||||
|
required Map<String, dynamic> appearance,
|
||||||
|
}) = _SnProgram;
|
||||||
|
|
||||||
|
factory SnProgram.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnProgramFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SnProgramMember with _$SnProgramMember {
|
||||||
|
const factory SnProgramMember({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
required DateTime lastPaid,
|
||||||
|
required SnAccount account,
|
||||||
|
required int accountId,
|
||||||
|
required SnProgram program,
|
||||||
|
required int programId,
|
||||||
|
}) = _SnProgramMember;
|
||||||
|
|
||||||
|
factory SnProgramMember.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnProgramMemberFromJson(json);
|
||||||
|
}
|
||||||
|
@@ -3470,4 +3470,763 @@ class __$SnActionEventCopyWithImpl<$Res>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnProgram {
|
||||||
|
int get id;
|
||||||
|
DateTime get createdAt;
|
||||||
|
DateTime get updatedAt;
|
||||||
|
DateTime? get deletedAt;
|
||||||
|
String get name;
|
||||||
|
String get description;
|
||||||
|
String get alias;
|
||||||
|
int get expRequirement;
|
||||||
|
Map<String, dynamic> get price;
|
||||||
|
Map<String, dynamic> get badge;
|
||||||
|
Map<String, dynamic> get group;
|
||||||
|
Map<String, dynamic> get appearance;
|
||||||
|
|
||||||
|
/// Create a copy of SnProgram
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnProgramCopyWith<SnProgram> get copyWith =>
|
||||||
|
_$SnProgramCopyWithImpl<SnProgram>(this as SnProgram, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnProgram to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is SnProgram &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
|
other.deletedAt == deletedAt) &&
|
||||||
|
(identical(other.name, name) || other.name == name) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
(identical(other.alias, alias) || other.alias == alias) &&
|
||||||
|
(identical(other.expRequirement, expRequirement) ||
|
||||||
|
other.expRequirement == expRequirement) &&
|
||||||
|
const DeepCollectionEquality().equals(other.price, price) &&
|
||||||
|
const DeepCollectionEquality().equals(other.badge, badge) &&
|
||||||
|
const DeepCollectionEquality().equals(other.group, group) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other.appearance, appearance));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
deletedAt,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
alias,
|
||||||
|
expRequirement,
|
||||||
|
const DeepCollectionEquality().hash(price),
|
||||||
|
const DeepCollectionEquality().hash(badge),
|
||||||
|
const DeepCollectionEquality().hash(group),
|
||||||
|
const DeepCollectionEquality().hash(appearance));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnProgram(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, name: $name, description: $description, alias: $alias, expRequirement: $expRequirement, price: $price, badge: $badge, group: $group, appearance: $appearance)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnProgramCopyWith<$Res> {
|
||||||
|
factory $SnProgramCopyWith(SnProgram value, $Res Function(SnProgram) _then) =
|
||||||
|
_$SnProgramCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
String alias,
|
||||||
|
int expRequirement,
|
||||||
|
Map<String, dynamic> price,
|
||||||
|
Map<String, dynamic> badge,
|
||||||
|
Map<String, dynamic> group,
|
||||||
|
Map<String, dynamic> appearance});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnProgramCopyWithImpl<$Res> implements $SnProgramCopyWith<$Res> {
|
||||||
|
_$SnProgramCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnProgram _self;
|
||||||
|
final $Res Function(SnProgram) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnProgram
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? alias = null,
|
||||||
|
Object? expRequirement = null,
|
||||||
|
Object? price = null,
|
||||||
|
Object? badge = null,
|
||||||
|
Object? group = null,
|
||||||
|
Object? appearance = null,
|
||||||
|
}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _self.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _self.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _self.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _self.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
name: null == name
|
||||||
|
? _self.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _self.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
alias: null == alias
|
||||||
|
? _self.alias
|
||||||
|
: alias // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
expRequirement: null == expRequirement
|
||||||
|
? _self.expRequirement
|
||||||
|
: expRequirement // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
price: null == price
|
||||||
|
? _self.price
|
||||||
|
: price // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
badge: null == badge
|
||||||
|
? _self.badge
|
||||||
|
: badge // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
group: null == group
|
||||||
|
? _self.group
|
||||||
|
: group // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
appearance: null == appearance
|
||||||
|
? _self.appearance
|
||||||
|
: appearance // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _SnProgram implements SnProgram {
|
||||||
|
const _SnProgram(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required this.alias,
|
||||||
|
required this.expRequirement,
|
||||||
|
required final Map<String, dynamic> price,
|
||||||
|
required final Map<String, dynamic> badge,
|
||||||
|
required final Map<String, dynamic> group,
|
||||||
|
required final Map<String, dynamic> appearance})
|
||||||
|
: _price = price,
|
||||||
|
_badge = badge,
|
||||||
|
_group = group,
|
||||||
|
_appearance = appearance;
|
||||||
|
factory _SnProgram.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnProgramFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
@override
|
||||||
|
final String name;
|
||||||
|
@override
|
||||||
|
final String description;
|
||||||
|
@override
|
||||||
|
final String alias;
|
||||||
|
@override
|
||||||
|
final int expRequirement;
|
||||||
|
final Map<String, dynamic> _price;
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> get price {
|
||||||
|
if (_price is EqualUnmodifiableMapView) return _price;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_price);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, dynamic> _badge;
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> get badge {
|
||||||
|
if (_badge is EqualUnmodifiableMapView) return _badge;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_badge);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, dynamic> _group;
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> get group {
|
||||||
|
if (_group is EqualUnmodifiableMapView) return _group;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, dynamic> _appearance;
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> get appearance {
|
||||||
|
if (_appearance is EqualUnmodifiableMapView) return _appearance;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnProgram
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnProgramCopyWith<_SnProgram> get copyWith =>
|
||||||
|
__$SnProgramCopyWithImpl<_SnProgram>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnProgramToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _SnProgram &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
|
other.deletedAt == deletedAt) &&
|
||||||
|
(identical(other.name, name) || other.name == name) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
(identical(other.alias, alias) || other.alias == alias) &&
|
||||||
|
(identical(other.expRequirement, expRequirement) ||
|
||||||
|
other.expRequirement == expRequirement) &&
|
||||||
|
const DeepCollectionEquality().equals(other._price, _price) &&
|
||||||
|
const DeepCollectionEquality().equals(other._badge, _badge) &&
|
||||||
|
const DeepCollectionEquality().equals(other._group, _group) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._appearance, _appearance));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
deletedAt,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
alias,
|
||||||
|
expRequirement,
|
||||||
|
const DeepCollectionEquality().hash(_price),
|
||||||
|
const DeepCollectionEquality().hash(_badge),
|
||||||
|
const DeepCollectionEquality().hash(_group),
|
||||||
|
const DeepCollectionEquality().hash(_appearance));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnProgram(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, name: $name, description: $description, alias: $alias, expRequirement: $expRequirement, price: $price, badge: $badge, group: $group, appearance: $appearance)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnProgramCopyWith<$Res>
|
||||||
|
implements $SnProgramCopyWith<$Res> {
|
||||||
|
factory _$SnProgramCopyWith(
|
||||||
|
_SnProgram value, $Res Function(_SnProgram) _then) =
|
||||||
|
__$SnProgramCopyWithImpl;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
String alias,
|
||||||
|
int expRequirement,
|
||||||
|
Map<String, dynamic> price,
|
||||||
|
Map<String, dynamic> badge,
|
||||||
|
Map<String, dynamic> group,
|
||||||
|
Map<String, dynamic> appearance});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnProgramCopyWithImpl<$Res> implements _$SnProgramCopyWith<$Res> {
|
||||||
|
__$SnProgramCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnProgram _self;
|
||||||
|
final $Res Function(_SnProgram) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnProgram
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? alias = null,
|
||||||
|
Object? expRequirement = null,
|
||||||
|
Object? price = null,
|
||||||
|
Object? badge = null,
|
||||||
|
Object? group = null,
|
||||||
|
Object? appearance = null,
|
||||||
|
}) {
|
||||||
|
return _then(_SnProgram(
|
||||||
|
id: null == id
|
||||||
|
? _self.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _self.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _self.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _self.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
name: null == name
|
||||||
|
? _self.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _self.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
alias: null == alias
|
||||||
|
? _self.alias
|
||||||
|
: alias // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
expRequirement: null == expRequirement
|
||||||
|
? _self.expRequirement
|
||||||
|
: expRequirement // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
price: null == price
|
||||||
|
? _self._price
|
||||||
|
: price // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
badge: null == badge
|
||||||
|
? _self._badge
|
||||||
|
: badge // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
group: null == group
|
||||||
|
? _self._group
|
||||||
|
: group // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
appearance: null == appearance
|
||||||
|
? _self._appearance
|
||||||
|
: appearance // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnProgramMember {
|
||||||
|
int get id;
|
||||||
|
DateTime get createdAt;
|
||||||
|
DateTime get updatedAt;
|
||||||
|
DateTime? get deletedAt;
|
||||||
|
DateTime get lastPaid;
|
||||||
|
SnAccount get account;
|
||||||
|
int get accountId;
|
||||||
|
SnProgram get program;
|
||||||
|
int get programId;
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnProgramMemberCopyWith<SnProgramMember> get copyWith =>
|
||||||
|
_$SnProgramMemberCopyWithImpl<SnProgramMember>(
|
||||||
|
this as SnProgramMember, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnProgramMember to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is SnProgramMember &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
|
other.deletedAt == deletedAt) &&
|
||||||
|
(identical(other.lastPaid, lastPaid) ||
|
||||||
|
other.lastPaid == lastPaid) &&
|
||||||
|
(identical(other.account, account) || other.account == account) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId) &&
|
||||||
|
(identical(other.program, program) || other.program == program) &&
|
||||||
|
(identical(other.programId, programId) ||
|
||||||
|
other.programId == programId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
|
deletedAt, lastPaid, account, accountId, program, programId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnProgramMember(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, lastPaid: $lastPaid, account: $account, accountId: $accountId, program: $program, programId: $programId)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnProgramMemberCopyWith<$Res> {
|
||||||
|
factory $SnProgramMemberCopyWith(
|
||||||
|
SnProgramMember value, $Res Function(SnProgramMember) _then) =
|
||||||
|
_$SnProgramMemberCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
DateTime lastPaid,
|
||||||
|
SnAccount account,
|
||||||
|
int accountId,
|
||||||
|
SnProgram program,
|
||||||
|
int programId});
|
||||||
|
|
||||||
|
$SnAccountCopyWith<$Res> get account;
|
||||||
|
$SnProgramCopyWith<$Res> get program;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnProgramMemberCopyWithImpl<$Res>
|
||||||
|
implements $SnProgramMemberCopyWith<$Res> {
|
||||||
|
_$SnProgramMemberCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnProgramMember _self;
|
||||||
|
final $Res Function(SnProgramMember) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? lastPaid = null,
|
||||||
|
Object? account = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? program = null,
|
||||||
|
Object? programId = null,
|
||||||
|
}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _self.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _self.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _self.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _self.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
lastPaid: null == lastPaid
|
||||||
|
? _self.lastPaid
|
||||||
|
: lastPaid // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
account: null == account
|
||||||
|
? _self.account
|
||||||
|
: account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _self.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
program: null == program
|
||||||
|
? _self.program
|
||||||
|
: program // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnProgram,
|
||||||
|
programId: null == programId
|
||||||
|
? _self.programId
|
||||||
|
: programId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountCopyWith<$Res> get account {
|
||||||
|
return $SnAccountCopyWith<$Res>(_self.account, (value) {
|
||||||
|
return _then(_self.copyWith(account: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnProgramCopyWith<$Res> get program {
|
||||||
|
return $SnProgramCopyWith<$Res>(_self.program, (value) {
|
||||||
|
return _then(_self.copyWith(program: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _SnProgramMember implements SnProgramMember {
|
||||||
|
const _SnProgramMember(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.lastPaid,
|
||||||
|
required this.account,
|
||||||
|
required this.accountId,
|
||||||
|
required this.program,
|
||||||
|
required this.programId});
|
||||||
|
factory _SnProgramMember.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnProgramMemberFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
@override
|
||||||
|
final DateTime lastPaid;
|
||||||
|
@override
|
||||||
|
final SnAccount account;
|
||||||
|
@override
|
||||||
|
final int accountId;
|
||||||
|
@override
|
||||||
|
final SnProgram program;
|
||||||
|
@override
|
||||||
|
final int programId;
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnProgramMemberCopyWith<_SnProgramMember> get copyWith =>
|
||||||
|
__$SnProgramMemberCopyWithImpl<_SnProgramMember>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnProgramMemberToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _SnProgramMember &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
|
other.deletedAt == deletedAt) &&
|
||||||
|
(identical(other.lastPaid, lastPaid) ||
|
||||||
|
other.lastPaid == lastPaid) &&
|
||||||
|
(identical(other.account, account) || other.account == account) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId) &&
|
||||||
|
(identical(other.program, program) || other.program == program) &&
|
||||||
|
(identical(other.programId, programId) ||
|
||||||
|
other.programId == programId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
|
deletedAt, lastPaid, account, accountId, program, programId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnProgramMember(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, lastPaid: $lastPaid, account: $account, accountId: $accountId, program: $program, programId: $programId)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnProgramMemberCopyWith<$Res>
|
||||||
|
implements $SnProgramMemberCopyWith<$Res> {
|
||||||
|
factory _$SnProgramMemberCopyWith(
|
||||||
|
_SnProgramMember value, $Res Function(_SnProgramMember) _then) =
|
||||||
|
__$SnProgramMemberCopyWithImpl;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
DateTime lastPaid,
|
||||||
|
SnAccount account,
|
||||||
|
int accountId,
|
||||||
|
SnProgram program,
|
||||||
|
int programId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnAccountCopyWith<$Res> get account;
|
||||||
|
@override
|
||||||
|
$SnProgramCopyWith<$Res> get program;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnProgramMemberCopyWithImpl<$Res>
|
||||||
|
implements _$SnProgramMemberCopyWith<$Res> {
|
||||||
|
__$SnProgramMemberCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnProgramMember _self;
|
||||||
|
final $Res Function(_SnProgramMember) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? lastPaid = null,
|
||||||
|
Object? account = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? program = null,
|
||||||
|
Object? programId = null,
|
||||||
|
}) {
|
||||||
|
return _then(_SnProgramMember(
|
||||||
|
id: null == id
|
||||||
|
? _self.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _self.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _self.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _self.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
lastPaid: null == lastPaid
|
||||||
|
? _self.lastPaid
|
||||||
|
: lastPaid // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
account: null == account
|
||||||
|
? _self.account
|
||||||
|
: account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _self.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
program: null == program
|
||||||
|
? _self.program
|
||||||
|
: program // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnProgram,
|
||||||
|
programId: null == programId
|
||||||
|
? _self.programId
|
||||||
|
: programId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountCopyWith<$Res> get account {
|
||||||
|
return $SnAccountCopyWith<$Res>(_self.account, (value) {
|
||||||
|
return _then(_self.copyWith(account: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnProgramMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnProgramCopyWith<$Res> get program {
|
||||||
|
return $SnProgramCopyWith<$Res>(_self.program, (value) {
|
||||||
|
return _then(_self.copyWith(program: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dart format on
|
// dart format on
|
||||||
|
@@ -319,3 +319,64 @@ Map<String, dynamic> _$SnActionEventToJson(_SnActionEvent instance) =>
|
|||||||
'account': instance.account.toJson(),
|
'account': instance.account.toJson(),
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_SnProgram _$SnProgramFromJson(Map<String, dynamic> json) => _SnProgram(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
name: json['name'] as String,
|
||||||
|
description: json['description'] as String,
|
||||||
|
alias: json['alias'] as String,
|
||||||
|
expRequirement: (json['exp_requirement'] as num).toInt(),
|
||||||
|
price: json['price'] as Map<String, dynamic>,
|
||||||
|
badge: json['badge'] as Map<String, dynamic>,
|
||||||
|
group: json['group'] as Map<String, dynamic>,
|
||||||
|
appearance: json['appearance'] as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnProgramToJson(_SnProgram instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
'alias': instance.alias,
|
||||||
|
'exp_requirement': instance.expRequirement,
|
||||||
|
'price': instance.price,
|
||||||
|
'badge': instance.badge,
|
||||||
|
'group': instance.group,
|
||||||
|
'appearance': instance.appearance,
|
||||||
|
};
|
||||||
|
|
||||||
|
_SnProgramMember _$SnProgramMemberFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnProgramMember(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
lastPaid: DateTime.parse(json['last_paid'] as String),
|
||||||
|
account: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
||||||
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
|
program: SnProgram.fromJson(json['program'] as Map<String, dynamic>),
|
||||||
|
programId: (json['program_id'] as num).toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnProgramMemberToJson(_SnProgramMember instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'last_paid': instance.lastPaid.toIso8601String(),
|
||||||
|
'account': instance.account.toJson(),
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'program': instance.program.toJson(),
|
||||||
|
'program_id': instance.programId,
|
||||||
|
};
|
||||||
|
@@ -11,6 +11,7 @@ abstract class SnWallet with _$SnWallet {
|
|||||||
required DateTime updatedAt,
|
required DateTime updatedAt,
|
||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
required String balance,
|
required String balance,
|
||||||
|
required String goldenBalance,
|
||||||
required String password,
|
required String password,
|
||||||
required int accountId,
|
required int accountId,
|
||||||
}) = _SnWallet;
|
}) = _SnWallet;
|
||||||
@@ -27,6 +28,7 @@ abstract class SnTransaction with _$SnTransaction {
|
|||||||
required DateTime? deletedAt,
|
required DateTime? deletedAt,
|
||||||
required String remark,
|
required String remark,
|
||||||
required String amount,
|
required String amount,
|
||||||
|
required String currency,
|
||||||
required SnWallet? payer,
|
required SnWallet? payer,
|
||||||
required SnWallet? payee,
|
required SnWallet? payee,
|
||||||
required int? payerId,
|
required int? payerId,
|
||||||
|
@@ -20,6 +20,7 @@ mixin _$SnWallet {
|
|||||||
DateTime get updatedAt;
|
DateTime get updatedAt;
|
||||||
DateTime? get deletedAt;
|
DateTime? get deletedAt;
|
||||||
String get balance;
|
String get balance;
|
||||||
|
String get goldenBalance;
|
||||||
String get password;
|
String get password;
|
||||||
int get accountId;
|
int get accountId;
|
||||||
|
|
||||||
@@ -46,6 +47,8 @@ mixin _$SnWallet {
|
|||||||
(identical(other.deletedAt, deletedAt) ||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
other.deletedAt == deletedAt) &&
|
other.deletedAt == deletedAt) &&
|
||||||
(identical(other.balance, balance) || other.balance == balance) &&
|
(identical(other.balance, balance) || other.balance == balance) &&
|
||||||
|
(identical(other.goldenBalance, goldenBalance) ||
|
||||||
|
other.goldenBalance == goldenBalance) &&
|
||||||
(identical(other.password, password) ||
|
(identical(other.password, password) ||
|
||||||
other.password == password) &&
|
other.password == password) &&
|
||||||
(identical(other.accountId, accountId) ||
|
(identical(other.accountId, accountId) ||
|
||||||
@@ -55,11 +58,11 @@ mixin _$SnWallet {
|
|||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
deletedAt, balance, password, accountId);
|
deletedAt, balance, goldenBalance, password, accountId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, password: $password, accountId: $accountId)';
|
return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, goldenBalance: $goldenBalance, password: $password, accountId: $accountId)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +77,7 @@ abstract mixin class $SnWalletCopyWith<$Res> {
|
|||||||
DateTime updatedAt,
|
DateTime updatedAt,
|
||||||
DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
String balance,
|
String balance,
|
||||||
|
String goldenBalance,
|
||||||
String password,
|
String password,
|
||||||
int accountId});
|
int accountId});
|
||||||
}
|
}
|
||||||
@@ -95,6 +99,7 @@ class _$SnWalletCopyWithImpl<$Res> implements $SnWalletCopyWith<$Res> {
|
|||||||
Object? updatedAt = null,
|
Object? updatedAt = null,
|
||||||
Object? deletedAt = freezed,
|
Object? deletedAt = freezed,
|
||||||
Object? balance = null,
|
Object? balance = null,
|
||||||
|
Object? goldenBalance = null,
|
||||||
Object? password = null,
|
Object? password = null,
|
||||||
Object? accountId = null,
|
Object? accountId = null,
|
||||||
}) {
|
}) {
|
||||||
@@ -119,6 +124,10 @@ class _$SnWalletCopyWithImpl<$Res> implements $SnWalletCopyWith<$Res> {
|
|||||||
? _self.balance
|
? _self.balance
|
||||||
: balance // ignore: cast_nullable_to_non_nullable
|
: balance // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
goldenBalance: null == goldenBalance
|
||||||
|
? _self.goldenBalance
|
||||||
|
: goldenBalance // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
password: null == password
|
password: null == password
|
||||||
? _self.password
|
? _self.password
|
||||||
: password // ignore: cast_nullable_to_non_nullable
|
: password // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -140,6 +149,7 @@ class _SnWallet implements SnWallet {
|
|||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
required this.deletedAt,
|
required this.deletedAt,
|
||||||
required this.balance,
|
required this.balance,
|
||||||
|
required this.goldenBalance,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.accountId});
|
required this.accountId});
|
||||||
factory _SnWallet.fromJson(Map<String, dynamic> json) =>
|
factory _SnWallet.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -156,6 +166,8 @@ class _SnWallet implements SnWallet {
|
|||||||
@override
|
@override
|
||||||
final String balance;
|
final String balance;
|
||||||
@override
|
@override
|
||||||
|
final String goldenBalance;
|
||||||
|
@override
|
||||||
final String password;
|
final String password;
|
||||||
@override
|
@override
|
||||||
final int accountId;
|
final int accountId;
|
||||||
@@ -188,6 +200,8 @@ class _SnWallet implements SnWallet {
|
|||||||
(identical(other.deletedAt, deletedAt) ||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
other.deletedAt == deletedAt) &&
|
other.deletedAt == deletedAt) &&
|
||||||
(identical(other.balance, balance) || other.balance == balance) &&
|
(identical(other.balance, balance) || other.balance == balance) &&
|
||||||
|
(identical(other.goldenBalance, goldenBalance) ||
|
||||||
|
other.goldenBalance == goldenBalance) &&
|
||||||
(identical(other.password, password) ||
|
(identical(other.password, password) ||
|
||||||
other.password == password) &&
|
other.password == password) &&
|
||||||
(identical(other.accountId, accountId) ||
|
(identical(other.accountId, accountId) ||
|
||||||
@@ -197,11 +211,11 @@ class _SnWallet implements SnWallet {
|
|||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
deletedAt, balance, password, accountId);
|
deletedAt, balance, goldenBalance, password, accountId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, password: $password, accountId: $accountId)';
|
return 'SnWallet(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, balance: $balance, goldenBalance: $goldenBalance, password: $password, accountId: $accountId)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +232,7 @@ abstract mixin class _$SnWalletCopyWith<$Res>
|
|||||||
DateTime updatedAt,
|
DateTime updatedAt,
|
||||||
DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
String balance,
|
String balance,
|
||||||
|
String goldenBalance,
|
||||||
String password,
|
String password,
|
||||||
int accountId});
|
int accountId});
|
||||||
}
|
}
|
||||||
@@ -239,6 +254,7 @@ class __$SnWalletCopyWithImpl<$Res> implements _$SnWalletCopyWith<$Res> {
|
|||||||
Object? updatedAt = null,
|
Object? updatedAt = null,
|
||||||
Object? deletedAt = freezed,
|
Object? deletedAt = freezed,
|
||||||
Object? balance = null,
|
Object? balance = null,
|
||||||
|
Object? goldenBalance = null,
|
||||||
Object? password = null,
|
Object? password = null,
|
||||||
Object? accountId = null,
|
Object? accountId = null,
|
||||||
}) {
|
}) {
|
||||||
@@ -263,6 +279,10 @@ class __$SnWalletCopyWithImpl<$Res> implements _$SnWalletCopyWith<$Res> {
|
|||||||
? _self.balance
|
? _self.balance
|
||||||
: balance // ignore: cast_nullable_to_non_nullable
|
: balance // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
goldenBalance: null == goldenBalance
|
||||||
|
? _self.goldenBalance
|
||||||
|
: goldenBalance // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
password: null == password
|
password: null == password
|
||||||
? _self.password
|
? _self.password
|
||||||
: password // ignore: cast_nullable_to_non_nullable
|
: password // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -283,6 +303,7 @@ mixin _$SnTransaction {
|
|||||||
DateTime? get deletedAt;
|
DateTime? get deletedAt;
|
||||||
String get remark;
|
String get remark;
|
||||||
String get amount;
|
String get amount;
|
||||||
|
String get currency;
|
||||||
SnWallet? get payer;
|
SnWallet? get payer;
|
||||||
SnWallet? get payee;
|
SnWallet? get payee;
|
||||||
int? get payerId;
|
int? get payerId;
|
||||||
@@ -313,6 +334,8 @@ mixin _$SnTransaction {
|
|||||||
other.deletedAt == deletedAt) &&
|
other.deletedAt == deletedAt) &&
|
||||||
(identical(other.remark, remark) || other.remark == remark) &&
|
(identical(other.remark, remark) || other.remark == remark) &&
|
||||||
(identical(other.amount, amount) || other.amount == amount) &&
|
(identical(other.amount, amount) || other.amount == amount) &&
|
||||||
|
(identical(other.currency, currency) ||
|
||||||
|
other.currency == currency) &&
|
||||||
(identical(other.payer, payer) || other.payer == payer) &&
|
(identical(other.payer, payer) || other.payer == payer) &&
|
||||||
(identical(other.payee, payee) || other.payee == payee) &&
|
(identical(other.payee, payee) || other.payee == payee) &&
|
||||||
(identical(other.payerId, payerId) || other.payerId == payerId) &&
|
(identical(other.payerId, payerId) || other.payerId == payerId) &&
|
||||||
@@ -322,11 +345,11 @@ mixin _$SnTransaction {
|
|||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
deletedAt, remark, amount, payer, payee, payerId, payeeId);
|
deletedAt, remark, amount, currency, payer, payee, payerId, payeeId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)';
|
return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, currency: $currency, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +366,7 @@ abstract mixin class $SnTransactionCopyWith<$Res> {
|
|||||||
DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
String remark,
|
String remark,
|
||||||
String amount,
|
String amount,
|
||||||
|
String currency,
|
||||||
SnWallet? payer,
|
SnWallet? payer,
|
||||||
SnWallet? payee,
|
SnWallet? payee,
|
||||||
int? payerId,
|
int? payerId,
|
||||||
@@ -371,6 +395,7 @@ class _$SnTransactionCopyWithImpl<$Res>
|
|||||||
Object? deletedAt = freezed,
|
Object? deletedAt = freezed,
|
||||||
Object? remark = null,
|
Object? remark = null,
|
||||||
Object? amount = null,
|
Object? amount = null,
|
||||||
|
Object? currency = null,
|
||||||
Object? payer = freezed,
|
Object? payer = freezed,
|
||||||
Object? payee = freezed,
|
Object? payee = freezed,
|
||||||
Object? payerId = freezed,
|
Object? payerId = freezed,
|
||||||
@@ -401,6 +426,10 @@ class _$SnTransactionCopyWithImpl<$Res>
|
|||||||
? _self.amount
|
? _self.amount
|
||||||
: amount // ignore: cast_nullable_to_non_nullable
|
: amount // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
currency: null == currency
|
||||||
|
? _self.currency
|
||||||
|
: currency // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
payer: freezed == payer
|
payer: freezed == payer
|
||||||
? _self.payer
|
? _self.payer
|
||||||
: payer // ignore: cast_nullable_to_non_nullable
|
: payer // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -459,6 +488,7 @@ class _SnTransaction implements SnTransaction {
|
|||||||
required this.deletedAt,
|
required this.deletedAt,
|
||||||
required this.remark,
|
required this.remark,
|
||||||
required this.amount,
|
required this.amount,
|
||||||
|
required this.currency,
|
||||||
required this.payer,
|
required this.payer,
|
||||||
required this.payee,
|
required this.payee,
|
||||||
required this.payerId,
|
required this.payerId,
|
||||||
@@ -479,6 +509,8 @@ class _SnTransaction implements SnTransaction {
|
|||||||
@override
|
@override
|
||||||
final String amount;
|
final String amount;
|
||||||
@override
|
@override
|
||||||
|
final String currency;
|
||||||
|
@override
|
||||||
final SnWallet? payer;
|
final SnWallet? payer;
|
||||||
@override
|
@override
|
||||||
final SnWallet? payee;
|
final SnWallet? payee;
|
||||||
@@ -516,6 +548,8 @@ class _SnTransaction implements SnTransaction {
|
|||||||
other.deletedAt == deletedAt) &&
|
other.deletedAt == deletedAt) &&
|
||||||
(identical(other.remark, remark) || other.remark == remark) &&
|
(identical(other.remark, remark) || other.remark == remark) &&
|
||||||
(identical(other.amount, amount) || other.amount == amount) &&
|
(identical(other.amount, amount) || other.amount == amount) &&
|
||||||
|
(identical(other.currency, currency) ||
|
||||||
|
other.currency == currency) &&
|
||||||
(identical(other.payer, payer) || other.payer == payer) &&
|
(identical(other.payer, payer) || other.payer == payer) &&
|
||||||
(identical(other.payee, payee) || other.payee == payee) &&
|
(identical(other.payee, payee) || other.payee == payee) &&
|
||||||
(identical(other.payerId, payerId) || other.payerId == payerId) &&
|
(identical(other.payerId, payerId) || other.payerId == payerId) &&
|
||||||
@@ -525,11 +559,11 @@ class _SnTransaction implements SnTransaction {
|
|||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
int get hashCode => Object.hash(runtimeType, id, createdAt, updatedAt,
|
||||||
deletedAt, remark, amount, payer, payee, payerId, payeeId);
|
deletedAt, remark, amount, currency, payer, payee, payerId, payeeId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)';
|
return 'SnTransaction(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, remark: $remark, amount: $amount, currency: $currency, payer: $payer, payee: $payee, payerId: $payerId, payeeId: $payeeId)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +582,7 @@ abstract mixin class _$SnTransactionCopyWith<$Res>
|
|||||||
DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
String remark,
|
String remark,
|
||||||
String amount,
|
String amount,
|
||||||
|
String currency,
|
||||||
SnWallet? payer,
|
SnWallet? payer,
|
||||||
SnWallet? payee,
|
SnWallet? payee,
|
||||||
int? payerId,
|
int? payerId,
|
||||||
@@ -578,6 +613,7 @@ class __$SnTransactionCopyWithImpl<$Res>
|
|||||||
Object? deletedAt = freezed,
|
Object? deletedAt = freezed,
|
||||||
Object? remark = null,
|
Object? remark = null,
|
||||||
Object? amount = null,
|
Object? amount = null,
|
||||||
|
Object? currency = null,
|
||||||
Object? payer = freezed,
|
Object? payer = freezed,
|
||||||
Object? payee = freezed,
|
Object? payee = freezed,
|
||||||
Object? payerId = freezed,
|
Object? payerId = freezed,
|
||||||
@@ -608,6 +644,10 @@ class __$SnTransactionCopyWithImpl<$Res>
|
|||||||
? _self.amount
|
? _self.amount
|
||||||
: amount // ignore: cast_nullable_to_non_nullable
|
: amount // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as String,
|
||||||
|
currency: null == currency
|
||||||
|
? _self.currency
|
||||||
|
: currency // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
payer: freezed == payer
|
payer: freezed == payer
|
||||||
? _self.payer
|
? _self.payer
|
||||||
: payer // ignore: cast_nullable_to_non_nullable
|
: payer // ignore: cast_nullable_to_non_nullable
|
||||||
|
@@ -14,6 +14,7 @@ _SnWallet _$SnWalletFromJson(Map<String, dynamic> json) => _SnWallet(
|
|||||||
? null
|
? null
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
balance: json['balance'] as String,
|
balance: json['balance'] as String,
|
||||||
|
goldenBalance: json['golden_balance'] as String,
|
||||||
password: json['password'] as String,
|
password: json['password'] as String,
|
||||||
accountId: (json['account_id'] as num).toInt(),
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
);
|
);
|
||||||
@@ -24,6 +25,7 @@ Map<String, dynamic> _$SnWalletToJson(_SnWallet instance) => <String, dynamic>{
|
|||||||
'updated_at': instance.updatedAt.toIso8601String(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'balance': instance.balance,
|
'balance': instance.balance,
|
||||||
|
'golden_balance': instance.goldenBalance,
|
||||||
'password': instance.password,
|
'password': instance.password,
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
};
|
};
|
||||||
@@ -38,6 +40,7 @@ _SnTransaction _$SnTransactionFromJson(Map<String, dynamic> json) =>
|
|||||||
: DateTime.parse(json['deleted_at'] as String),
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
remark: json['remark'] as String,
|
remark: json['remark'] as String,
|
||||||
amount: json['amount'] as String,
|
amount: json['amount'] as String,
|
||||||
|
currency: json['currency'] as String,
|
||||||
payer: json['payer'] == null
|
payer: json['payer'] == null
|
||||||
? null
|
? null
|
||||||
: SnWallet.fromJson(json['payer'] as Map<String, dynamic>),
|
: SnWallet.fromJson(json['payer'] as Map<String, dynamic>),
|
||||||
@@ -56,6 +59,7 @@ Map<String, dynamic> _$SnTransactionToJson(_SnTransaction instance) =>
|
|||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'remark': instance.remark,
|
'remark': instance.remark,
|
||||||
'amount': instance.amount,
|
'amount': instance.amount,
|
||||||
|
'currency': instance.currency,
|
||||||
'payer': instance.payer?.toJson(),
|
'payer': instance.payer?.toJson(),
|
||||||
'payee': instance.payee?.toJson(),
|
'payee': instance.payee?.toJson(),
|
||||||
'payer_id': instance.payerId,
|
'payer_id': instance.payerId,
|
||||||
|
@@ -16,7 +16,12 @@ class ConnectionIndicator extends StatelessWidget {
|
|||||||
final ws = context.watch<WebSocketProvider>();
|
final ws = context.watch<WebSocketProvider>();
|
||||||
final cfg = context.watch<ConfigProvider>();
|
final cfg = context.watch<ConfigProvider>();
|
||||||
|
|
||||||
final marginLeft = cfg.drawerIsCollapsed ? 0.0 : cfg.drawerIsExpanded ? 304.0 : 80.0;
|
final marginLeft =
|
||||||
|
cfg.drawerIsCollapsed
|
||||||
|
? 0.0
|
||||||
|
: cfg.drawerIsExpanded
|
||||||
|
? 304.0
|
||||||
|
: 80.0;
|
||||||
|
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: ws,
|
listenable: ws,
|
||||||
@@ -32,26 +37,31 @@ class ConnectionIndicator extends StatelessWidget {
|
|||||||
elevation: 2,
|
elevation: 2,
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
child: ua.isAuthorized
|
child:
|
||||||
|
ua.isAuthorized
|
||||||
? Row(
|
? Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (ws.isBusy)
|
if (ws.isBusy)
|
||||||
Text('serverConnecting').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
Text(
|
||||||
|
'serverConnecting',
|
||||||
|
).tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
||||||
else if (!ws.isConnected)
|
else if (!ws.isConnected)
|
||||||
Text('serverDisconnected')
|
Text(
|
||||||
.tr()
|
'serverDisconnected',
|
||||||
.textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
).tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer)
|
||||||
else
|
else
|
||||||
Text('serverConnected').tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer),
|
Text(
|
||||||
|
'serverConnected',
|
||||||
|
).tr().textColor(Theme.of(context).colorScheme.onSecondaryContainer),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
if (ws.isBusy)
|
if (ws.isBusy)
|
||||||
const CircularProgressIndicator(strokeWidth: 2.5)
|
const CircularProgressIndicator(
|
||||||
.width(12)
|
strokeWidth: 2.5,
|
||||||
.height(12)
|
padding: EdgeInsets.zero,
|
||||||
.padding(horizontal: 4, right: 4)
|
).width(12).height(12).padding(horizontal: 4, right: 4)
|
||||||
else if (!ws.isConnected)
|
else if (!ws.isConnected)
|
||||||
const Icon(Symbols.power_off, size: 18)
|
const Icon(Symbols.power_off, size: 18)
|
||||||
else
|
else
|
||||||
@@ -59,10 +69,7 @@ class ConnectionIndicator extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 8, vertical: 4)
|
).padding(horizontal: 8, vertical: 4)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
).opacity(show ? 1 : 0, animate: true).animate(
|
).opacity(show ? 1 : 0, animate: true).animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||||
const Duration(milliseconds: 300),
|
|
||||||
Curves.easeInOut,
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (!ws.isConnected && !ws.isBusy) {
|
if (!ws.isConnected && !ws.isBusy) {
|
||||||
ws.connect();
|
ws.connect();
|
||||||
|
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -9,6 +10,7 @@ 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:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/channel.dart';
|
import 'package:surface/providers/channel.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
@@ -46,6 +48,17 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
final nav = context.watch<NavigationProvider>();
|
final nav = context.watch<NavigationProvider>();
|
||||||
final cfg = context.watch<ConfigProvider>();
|
final cfg = context.watch<ConfigProvider>();
|
||||||
|
|
||||||
|
final routeName = GoRouter.of(context)
|
||||||
|
.routerDelegate
|
||||||
|
.currentConfiguration
|
||||||
|
.last
|
||||||
|
.route
|
||||||
|
.name;
|
||||||
|
final showNavButtons = cfg.hideBottomNav ||
|
||||||
|
!(nav.showBottomNavScreen.contains(routeName)
|
||||||
|
? ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE)
|
||||||
|
: false);
|
||||||
|
|
||||||
final backgroundColor = cfg.drawerIsExpanded ? Colors.transparent : null;
|
final backgroundColor = cfg.drawerIsExpanded ? Colors.transparent : null;
|
||||||
|
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
@@ -54,6 +67,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
return Drawer(
|
return Drawer(
|
||||||
elevation: widget.elevation,
|
elevation: widget.elevation,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(0))),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -78,40 +92,61 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _DrawerContentList(),
|
child: _DrawerContentList(),
|
||||||
),
|
),
|
||||||
if (cfg.hideBottomNav)
|
if (showNavButtons)
|
||||||
Row(
|
Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: nav.destinations.where((ele) => ele.isPinned).map(
|
children:
|
||||||
(ele) {
|
nav.destinations.where((ele) => ele.isPinned).mapIndexed(
|
||||||
|
(idx, ele) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: ele.label.tr(),
|
message: ele.label.tr(),
|
||||||
child: IconButton.filledTonal(
|
child: IconButton(
|
||||||
icon: ele.icon,
|
icon: ele.icon,
|
||||||
color: Theme.of(context)
|
color: nav.currentIndex == idx
|
||||||
|
? Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onPrimaryContainer,
|
.onPrimaryContainer
|
||||||
|
: Theme.of(context).colorScheme.onSurface,
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(
|
||||||
|
nav.currentIndex == idx
|
||||||
|
? Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primaryContainer
|
||||||
|
: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
GoRouter.of(context).goNamed(ele.screen);
|
GoRouter.of(context).goNamed(ele.screen);
|
||||||
Scaffold.of(context).closeDrawer();
|
Scaffold.of(context).closeDrawer();
|
||||||
|
nav.setIndex(idx);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).toList(),
|
).toList(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16, bottom: 8),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
leading: AccountImage(content: ua.user?.avatar),
|
leading: AccountImage(
|
||||||
title: Text(ua.user?.nick ?? 'unknown'.tr()).fontSize(15),
|
content: ua.user?.avatar,
|
||||||
subtitle:
|
fallbackWidget:
|
||||||
Text('@${ua.user?.name ?? 'unknown'.tr()}').fontSize(13),
|
ua.isAuthorized ? null : const Icon(Symbols.login),
|
||||||
|
),
|
||||||
|
title: ua.isAuthorized
|
||||||
|
? Text(ua.user?.nick ?? 'unknown'.tr()).fontSize(15)
|
||||||
|
: Text('screenAuthLogin').tr(),
|
||||||
|
subtitle: ua.isAuthorized
|
||||||
|
? Text('@${ua.user?.name ?? 'unknown'.tr()}').fontSize(13)
|
||||||
|
: Text('navBottomUnauthorizedCaption').fontSize(13).tr(),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (ua.isAuthorized)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.notifications, fill: 1),
|
icon: const Icon(Symbols.notifications, fill: 1),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
@@ -138,7 +173,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Gap(MediaQuery.of(context).padding.bottom),
|
Gap(MediaQuery.of(context).padding.bottom + 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -155,7 +190,7 @@ class _DrawerContentList extends StatelessWidget {
|
|||||||
final ct = context.read<ChatChannelProvider>();
|
final ct = context.read<ChatChannelProvider>();
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final nav = context.watch<NavigationProvider>();
|
final nav = context.watch<NavigationProvider>();
|
||||||
final rel = context.read<SnRealmProvider>();
|
final rel = context.watch<SnRealmProvider>();
|
||||||
|
|
||||||
return PageTransitionSwitcher(
|
return PageTransitionSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
@@ -185,16 +220,6 @@ class _DrawerContentList extends StatelessWidget {
|
|||||||
horizontal: 32,
|
horizontal: 32,
|
||||||
vertical: 12,
|
vertical: 12,
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
contentPadding: EdgeInsets.only(left: 28, right: 16),
|
|
||||||
leading: const Icon(Symbols.home),
|
|
||||||
title: Text('screenHome').tr(),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).goNamed('home');
|
|
||||||
Scaffold.of(context).closeDrawer();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
...rel.availableRealms.map((ele) {
|
...rel.availableRealms.map((ele) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
@@ -209,6 +234,16 @@ class _DrawerContentList extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
contentPadding: EdgeInsets.only(left: 28, right: 16),
|
||||||
|
leading: const Icon(Symbols.globe).padding(right: 4),
|
||||||
|
title: Text('screenRealmDiscovery').tr(),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('realmDiscovery');
|
||||||
|
Scaffold.of(context).closeDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: ListView(
|
: ListView(
|
||||||
@@ -247,7 +282,7 @@ class _DrawerContentList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
title: Text(nav.focusedRealm!.name),
|
title: Text(nav.focusedRealm!.name),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).goNamed(
|
||||||
'realmDetail',
|
'realmDetail',
|
||||||
pathParameters: {
|
pathParameters: {
|
||||||
'alias': nav.focusedRealm!.alias,
|
'alias': nav.focusedRealm!.alias,
|
||||||
@@ -265,7 +300,7 @@ class _DrawerContentList extends StatelessWidget {
|
|||||||
leading: const Icon(Symbols.globe),
|
leading: const Icon(Symbols.globe),
|
||||||
title: Text('community').tr(),
|
title: Text('community').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).goNamed(
|
||||||
'realmCommunity',
|
'realmCommunity',
|
||||||
pathParameters: {
|
pathParameters: {
|
||||||
'alias': nav.focusedRealm!.alias,
|
'alias': nav.focusedRealm!.alias,
|
||||||
@@ -290,7 +325,7 @@ class _DrawerContentList extends StatelessWidget {
|
|||||||
leading: const Icon(Symbols.tag),
|
leading: const Icon(Symbols.tag),
|
||||||
title: Text(ele.name),
|
title: Text(ele.name),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).goNamed(
|
||||||
'chatRoom',
|
'chatRoom',
|
||||||
pathParameters: {
|
pathParameters: {
|
||||||
'scope': ele.realm?.alias ?? 'global',
|
'scope': ele.realm?.alias ?? 'global',
|
||||||
|
@@ -103,7 +103,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.client
|
await sn.client
|
||||||
.put('/cgi/co/questions/${widget.parentPost.id}/answer', data: {
|
.put('/cgi/co/questions/${widget.parentPost.id}/answer', data: {
|
||||||
'publisher': answer.publisherId,
|
'publisher': widget.parentPost.publisherId,
|
||||||
'answer_id': answer.id,
|
'answer_id': answer.id,
|
||||||
});
|
});
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
@@ -279,6 +279,8 @@ class _PostItemState extends State<PostItem> {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
final isAuthor =
|
final isAuthor =
|
||||||
ua.isAuthorized && widget.data.publisher.accountId == ua.user?.id;
|
ua.isAuthorized && widget.data.publisher.accountId == ua.user?.id;
|
||||||
|
final isParentAuthor = ua.isAuthorized &&
|
||||||
|
widget.data.replyTo?.publisher.accountId == ua.user?.id;
|
||||||
|
|
||||||
final displayableAttachments = widget.data.preload?.attachments
|
final displayableAttachments = widget.data.preload?.attachments
|
||||||
?.where((ele) =>
|
?.where((ele) =>
|
||||||
@@ -333,6 +335,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
_PostActionPopup(
|
_PostActionPopup(
|
||||||
data: widget.data,
|
data: widget.data,
|
||||||
isAuthor: isAuthor,
|
isAuthor: isAuthor,
|
||||||
|
isParentAuthor: isParentAuthor,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
onSelectAnswer: widget.onSelectAnswer,
|
onSelectAnswer: widget.onSelectAnswer,
|
||||||
@@ -577,6 +580,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
_PostActionPopup(
|
_PostActionPopup(
|
||||||
data: widget.data,
|
data: widget.data,
|
||||||
isAuthor: isAuthor,
|
isAuthor: isAuthor,
|
||||||
|
isParentAuthor: isParentAuthor,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
onSelectAnswer: widget.onSelectAnswer,
|
onSelectAnswer: widget.onSelectAnswer,
|
||||||
@@ -1317,6 +1321,7 @@ class _PostAvatar extends StatelessWidget {
|
|||||||
class _PostActionPopup extends StatelessWidget {
|
class _PostActionPopup extends StatelessWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final bool isAuthor;
|
final bool isAuthor;
|
||||||
|
final bool isParentAuthor;
|
||||||
final Function onDeleted;
|
final Function onDeleted;
|
||||||
final Function() onShare, onShareImage;
|
final Function() onShare, onShareImage;
|
||||||
final Function()? onSelectAnswer;
|
final Function()? onSelectAnswer;
|
||||||
@@ -1324,6 +1329,7 @@ class _PostActionPopup extends StatelessWidget {
|
|||||||
const _PostActionPopup({
|
const _PostActionPopup({
|
||||||
required this.data,
|
required this.data,
|
||||||
required this.isAuthor,
|
required this.isAuthor,
|
||||||
|
required this.isParentAuthor,
|
||||||
required this.onDeleted,
|
required this.onDeleted,
|
||||||
required this.onShare,
|
required this.onShare,
|
||||||
required this.onShareImage,
|
required this.onShareImage,
|
||||||
@@ -1397,7 +1403,7 @@ class _PostActionPopup extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (onTranslate != null) PopupMenuDivider(),
|
if (onTranslate != null) PopupMenuDivider(),
|
||||||
if (isAuthor && onSelectAnswer != null)
|
if (isParentAuthor && onSelectAnswer != null)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -1410,7 +1416,7 @@ class _PostActionPopup extends StatelessWidget {
|
|||||||
onSelectAnswer?.call();
|
onSelectAnswer?.call();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (isAuthor && onSelectAnswer != null) PopupMenuDivider(),
|
if (isParentAuthor && onSelectAnswer != null) PopupMenuDivider(),
|
||||||
if (isAuthor)
|
if (isAuthor)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@@ -2238,10 +2238,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: tray_manager
|
name: tray_manager
|
||||||
sha256: "80be6c508159a6f3c57983de795209ac13453e9832fd574143b06dceee188ed2"
|
sha256: c2da0f0f1ddb455e721cf68d05d1281fec75cf5df0a1d3cb67b6ca0bdfd5709d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.2"
|
version: "0.4.0"
|
||||||
tuple:
|
tuple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@@ -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: 2.4.2+83
|
version: 2.4.2+84
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@@ -59,7 +59,7 @@ dependencies:
|
|||||||
relative_time: ^5.0.0
|
relative_time: ^5.0.0
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
cross_file: ^0.3.4+2
|
cross_file: ^0.3.4+2
|
||||||
file_picker: ^9.0.0 # pinned due to compile failed on android, https://github.com/miguelpruivo/flutter_file_picker/issues/1643
|
file_picker: ^9.2.1
|
||||||
croppy: ^1.3.1
|
croppy: ^1.3.1
|
||||||
flutter_expandable_fab: ^2.3.0
|
flutter_expandable_fab: ^2.3.0
|
||||||
dropdown_button2: ^2.3.9
|
dropdown_button2: ^2.3.9
|
||||||
@@ -103,7 +103,7 @@ dependencies:
|
|||||||
flutter_svg: ^2.0.16
|
flutter_svg: ^2.0.16
|
||||||
home_widget: ^0.7.0
|
home_widget: ^0.7.0
|
||||||
receive_sharing_intent: ^1.8.1
|
receive_sharing_intent: ^1.8.1
|
||||||
workmanager:
|
workmanager: # use git due to: https://github.com/fluttercommunity/flutter_workmanager/issues/588#issuecomment-2660871645
|
||||||
git:
|
git:
|
||||||
url: https://github.com/fluttercommunity/flutter_workmanager.git
|
url: https://github.com/fluttercommunity/flutter_workmanager.git
|
||||||
path: workmanager
|
path: workmanager
|
||||||
@@ -120,7 +120,7 @@ dependencies:
|
|||||||
flutter_inappwebview: ^6.1.5
|
flutter_inappwebview: ^6.1.5
|
||||||
html: ^0.15.5
|
html: ^0.15.5
|
||||||
xml: ^6.5.0
|
xml: ^6.5.0
|
||||||
tray_manager: ^0.3.2
|
tray_manager: ^0.4.0
|
||||||
hotkey_manager: ^0.2.3
|
hotkey_manager: ^0.2.3
|
||||||
image_picker_android: ^0.8.12+20
|
image_picker_android: ^0.8.12+20
|
||||||
cached_network_image_platform_interface: ^4.1.1
|
cached_network_image_platform_interface: ^4.1.1
|
||||||
@@ -179,6 +179,7 @@ flutter:
|
|||||||
- assets/icon/icon-light-radius.png
|
- assets/icon/icon-light-radius.png
|
||||||
- assets/icon/tray-icon.ico
|
- assets/icon/tray-icon.ico
|
||||||
- assets/icon/tray-icon.png
|
- assets/icon/tray-icon.png
|
||||||
|
- assets/icon/kanban-1st.jpg
|
||||||
- assets/translations/
|
- assets/translations/
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
2
web/_redirects
Normal file
2
web/_redirects
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/assets/assets/translations/en.json /assets/assets/translations/en-US.json 301
|
||||||
|
/assets/assets/translations/zh.json /assets/assets/translations/zh-CN.json 301
|
Reference in New Issue
Block a user