Compare commits
25 Commits
2.3.2+67
...
365f330629
| Author | SHA1 | Date | |
|---|---|---|---|
| 365f330629 | |||
| a7829d15b2 | |||
| a3868a4281 | |||
|
|
1d1d61d60c | ||
| 03c2491587 | |||
| 2c1adc988c | |||
| c0fbee55e4 | |||
| 6e544c0b6c | |||
| 7d56c5ef31 | |||
| c2df1af16d | |||
| a8143c6453 | |||
| 04065061e0 | |||
| 226eb452e5 | |||
| a6715b0872 | |||
| 43e3404dbb | |||
| c91cf7c813 | |||
|
|
9cd1cad695 | ||
|
|
dde280833b | ||
| 42ac12b53e | |||
| 63567bf708 | |||
| 5d3cadefef | |||
| 251fbb2503 | |||
| 0b31d32217 | |||
| 5ddd4fed2e | |||
| 48b6d5f6c1 |
23
.github/workflows/nightly.yml
vendored
23
.github/workflows/nightly.yml
vendored
@@ -39,3 +39,26 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: build-output-windows
|
name: build-output-windows
|
||||||
path: build/windows/x64/runner/Release
|
path: build/windows/x64/runner/Release
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
cache: true
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update -y
|
||||||
|
sudo apt-get install -y ninja-build libgtk-3-dev
|
||||||
|
sudo apt-get install libmpv-dev mpv
|
||||||
|
sudo apt-get install libayatana-appindicator3-dev
|
||||||
|
sudo apt-get install keybinder-3.0
|
||||||
|
- run: flutter pub get
|
||||||
|
- run: flutter build linux
|
||||||
|
- name: Archive production artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build-output-linux
|
||||||
|
path: build/linux/x64/release/bundle
|
||||||
27
README.md
Normal file
27
README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Solar Network
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Hello there! Welcome to the main repository of the HyperNet (also known as the Solar Network). The code here is mainly about the frontend app (also known as Solian). But you can still post issues here to get help and request new features!
|
||||||
|
|
||||||
|
## Sub Projects
|
||||||
|
|
||||||
|
HyperNet, the Solar Network is a microservices project in which the backends are stored in separate repositories. Here is a simple index for it.
|
||||||
|
|
||||||
|
- The Core, Gateway: [Nexus](https://github.com/Solsynth/HyperNet.Nexus)
|
||||||
|
- The Auth Service: [Passport](https://github.com/Solsynth/HyperNet.Passport)
|
||||||
|
- The Posting Service: [Interactive](https://github.com/Solsynth/HyperNet.Interactive)
|
||||||
|
- The Messaging Service: [Messaging](https://github.com/Solsynth/HyperNet.Messaging)
|
||||||
|
- The Wallet Service: [Wallet](https://github.com/Solsynth/HyperNet.Wallet)
|
||||||
|
- The Crawler: [Reader](https://github.com/Solsynth/HyperNet.Reader)
|
||||||
|
- Some others may not be listed, you can search in the organization with `HyperNet.` the prefix of all HyperNet projects.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
For those people who want to know the tech stack of this project, the frontend was built by Flutter, which provides the cross-platform ability.
|
||||||
|
|
||||||
|
The backend was built in Go and PostgreSQL with our very own microservice framework included in the nexus.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
The readme will be updated in the future, to be determined. For now, you can check out the link of this repository to learn more on our official website.
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
android:label="Solian"
|
android:label="Solian"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:enableOnBackInvokedCallback="true"
|
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class CheckInWidget : GlanceAppWidget() {
|
|||||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||||
.create()
|
.create()
|
||||||
val resultTierSymbols = listOf("大凶", "凶", "中平", "吉", "大吉")
|
val resultTierSymbols = listOf("Bad", "Poor", "Medium", "Good", "Great")
|
||||||
|
|
||||||
val prefs = currentState.preferences
|
val prefs = currentState.preferences
|
||||||
val checkInRaw: String? = prefs.getString("pas_check_in_record", null)
|
val checkInRaw: String? = prefs.getString("pas_check_in_record", null)
|
||||||
@@ -120,7 +120,7 @@ class CheckInWidget : GlanceAppWidget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "You haven't checked in today",
|
text = "You haven't divined today",
|
||||||
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
|
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: {{endpoint}}/cgi/id/dev/notify/1
|
url: {{endpoint}}/cgi/id/dev/notify/122
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
@@ -15,12 +15,9 @@ body:json {
|
|||||||
"client_id": "{{third_client_id}}",
|
"client_id": "{{third_client_id}}",
|
||||||
"client_secret":"{{third_client_tk}}",
|
"client_secret":"{{third_client_tk}}",
|
||||||
"type": "general",
|
"type": "general",
|
||||||
"subject": "测试",
|
"subject": "处理该帐号 @solian 的决定",
|
||||||
"subtitle": "Alphabot です",
|
"subtitle": "违反用户协议",
|
||||||
"content": "全新通知动画",
|
"content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。",
|
||||||
"metadata": {
|
|
||||||
"image": "D2EDbcrsTugs3xk5"
|
|
||||||
},
|
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ body:json {
|
|||||||
"client_id": "alphabot",
|
"client_id": "alphabot",
|
||||||
"client_secret": "_uR0sVnHTh",
|
"client_secret": "_uR0sVnHTh",
|
||||||
"remark": "新年红包",
|
"remark": "新年红包",
|
||||||
"amount": 9705,
|
"amount": 150,
|
||||||
"payee_id": 2
|
"payee_id": 18
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,7 +419,7 @@
|
|||||||
"callMessageEnded": "Call lasted {}",
|
"callMessageEnded": "Call lasted {}",
|
||||||
"callMessageStarted": "Call started",
|
"callMessageStarted": "Call started",
|
||||||
"dailyCheckIn": "Check In",
|
"dailyCheckIn": "Check In",
|
||||||
"dailyCheckInNone": "You haven't checked in today",
|
"dailyCheckInNone": "You haven't divined today",
|
||||||
"dailyCheckAction": "Check in right now!",
|
"dailyCheckAction": "Check in right now!",
|
||||||
"dailyCheckDetail": "Can't understand the symbol? Master, help me understand it!",
|
"dailyCheckDetail": "Can't understand the symbol? Master, help me understand it!",
|
||||||
"dailyCheckDetailTitle": "{}'s fortune details",
|
"dailyCheckDetailTitle": "{}'s fortune details",
|
||||||
@@ -638,5 +638,17 @@
|
|||||||
"pollVotes": {
|
"pollVotes": {
|
||||||
"one": "{} vote",
|
"one": "{} vote",
|
||||||
"other": "{} votes"
|
"other": "{} votes"
|
||||||
}
|
},
|
||||||
|
"publisherDelete": "Delete Publisher {}",
|
||||||
|
"publisherDeleteDescription": "Are you sure you want to delete this publisher? This operation is irreversible.",
|
||||||
|
"channelIsPublic": "Public Channel",
|
||||||
|
"channelIsPublicDescription": "The channel is public, anyone can join.",
|
||||||
|
"channelIsCommunity": "Community Channel",
|
||||||
|
"channelIsCommunityDescription": "Currently, community channel has nothing special yet.",
|
||||||
|
"realmIsPublic": "Public Realm",
|
||||||
|
"realmIsPublicDescription": "The realm is public, anyone can join.",
|
||||||
|
"realmIsCommunity": "Community Realm",
|
||||||
|
"realmIsCommunityDescription": "Community realm will be displayed on the discover page.",
|
||||||
|
"realmLeave": "Leave Realm",
|
||||||
|
"realmLeaveDescription": "Leave the current realm and delete the realm's identity."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -637,5 +637,17 @@
|
|||||||
"pollVotes": {
|
"pollVotes": {
|
||||||
"one": "{} 票",
|
"one": "{} 票",
|
||||||
"other": "{} 票"
|
"other": "{} 票"
|
||||||
}
|
},
|
||||||
|
"publisherDelete": "删除发布者 {}",
|
||||||
|
"publisherDeleteDescription": "你确定要删除这个发布者吗?该操作不可撤销。",
|
||||||
|
"channelIsPublic": "公开频道",
|
||||||
|
"channelIsPublicDescription": "该频道是公开的,任何人都可以加入。",
|
||||||
|
"channelIsCommunity": "社区频道",
|
||||||
|
"channelIsCommunityDescription": "目前来说,社区频道还没有什么特别之处。",
|
||||||
|
"realmIsPublic": "公开领域",
|
||||||
|
"realmIsPublicDescription": "该领域是公开的,任何人都可以加入。",
|
||||||
|
"realmIsCommunity": "社区领域",
|
||||||
|
"realmIsCommunityDescription": "社区领域会显示在发现页面上。",
|
||||||
|
"realmLeave": "离开领域",
|
||||||
|
"realmLeaveDescription": "离开当前领域,并且删除领域中的身份。"
|
||||||
}
|
}
|
||||||
|
|||||||
14
debian/debian.yml
vendored
Normal file
14
debian/debian.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
flutter_app:
|
||||||
|
command: surface
|
||||||
|
arch: x64
|
||||||
|
parent: /usr/local/lib
|
||||||
|
nonInteractive: false
|
||||||
|
|
||||||
|
control:
|
||||||
|
Package: solian
|
||||||
|
Version: 2.3.2
|
||||||
|
Architecture: amd64
|
||||||
|
Priority: optional
|
||||||
|
Depends: mpv keybinder-3.0
|
||||||
|
Maintainer: Solsynth LLC
|
||||||
|
Description: The Solar Network Desktop Application
|
||||||
9
debian/gui/surface.desktop
vendored
Normal file
9
debian/gui/surface.desktop
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=2.3.2
|
||||||
|
Name=Solian
|
||||||
|
GenericName=Solian
|
||||||
|
Comment=The Solar Network Desktop Application
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Social Networking
|
||||||
|
Keywords=social;social network;chat;solar network
|
||||||
23
debian/gui/surface.svg
vendored
Normal file
23
debian/gui/surface.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 232 KiB |
@@ -55,7 +55,7 @@ struct CheckInEntry: TimelineEntry {
|
|||||||
struct CheckInWidgetEntryView : View {
|
struct CheckInWidgetEntryView : View {
|
||||||
var entry: CheckInProvider.Entry
|
var entry: CheckInProvider.Entry
|
||||||
|
|
||||||
private let resultTierSymbols: [String] = ["大凶", "凶", "中平", "吉", "大吉"]
|
private let resultTierSymbols: [String] = ["Bad", "Poor", "Medium", "Good", "Great"]
|
||||||
|
|
||||||
func checkIn() -> Void {}
|
func checkIn() -> Void {}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ struct CheckInWidgetEntryView : View {
|
|||||||
} else {
|
} else {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Check In").font(.system(size: 19, weight: .bold))
|
Text("Check In").font(.system(size: 19, weight: .bold))
|
||||||
Text("You haven't check in today").font(.system(size: 15))
|
Text("You haven't divined today").font(.system(size: 15))
|
||||||
}.padding(.horizontal, 4)
|
}.padding(.horizontal, 4)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import 'package:tray_manager/tray_manager.dart';
|
|||||||
import 'package:version/version.dart';
|
import 'package:version/version.dart';
|
||||||
import 'package:workmanager/workmanager.dart';
|
import 'package:workmanager/workmanager.dart';
|
||||||
import 'package:in_app_review/in_app_review.dart';
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
|
import 'package:image_picker_android/image_picker_android.dart';
|
||||||
|
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void appBackgroundDispatcher() {
|
void appBackgroundDispatcher() {
|
||||||
@@ -67,20 +69,6 @@ void appBackgroundDispatcher() {
|
|||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await EasyLocalization.ensureInitialized();
|
|
||||||
|
|
||||||
await Hive.initFlutter();
|
|
||||||
Hive.registerAdapter(SnChannelImplAdapter());
|
|
||||||
Hive.registerAdapter(SnRealmImplAdapter());
|
|
||||||
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
|
||||||
Hive.registerAdapter(SnChatMessageImplAdapter());
|
|
||||||
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
|
|
||||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
|
||||||
usePathUrlStrategy();
|
|
||||||
|
|
||||||
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||||
doWhenWindowReady(() {
|
doWhenWindowReady(() {
|
||||||
@@ -91,6 +79,23 @@ void main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EasyLocalization.ensureInitialized();
|
||||||
|
|
||||||
|
await Hive.initFlutter();
|
||||||
|
Hive.registerAdapter(SnChannelImplAdapter());
|
||||||
|
Hive.registerAdapter(SnRealmImplAdapter());
|
||||||
|
Hive.registerAdapter(SnChannelMemberImplAdapter());
|
||||||
|
Hive.registerAdapter(SnChatMessageImplAdapter());
|
||||||
|
|
||||||
|
if (kIsWeb && !Platform.isLinux) {
|
||||||
|
await Firebase.initializeApp(
|
||||||
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||||
|
usePathUrlStrategy();
|
||||||
|
|
||||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
Workmanager().initialize(
|
Workmanager().initialize(
|
||||||
appBackgroundDispatcher,
|
appBackgroundDispatcher,
|
||||||
@@ -107,6 +112,13 @@ void main() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!kIsWeb && Platform.isAndroid) {
|
||||||
|
final ImagePickerPlatform imagePickerImplementation = ImagePickerPlatform.instance;
|
||||||
|
if (imagePickerImplementation is ImagePickerAndroid) {
|
||||||
|
imagePickerImplementation.useAndroidPhotoPicker = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(const SolianApp());
|
runApp(const SolianApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +172,8 @@ class SolianApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
breakpoints: [
|
breakpoints: [
|
||||||
const Breakpoint(start: 0, end: 450, name: MOBILE),
|
const Breakpoint(start: 0, end: 600, name: MOBILE),
|
||||||
const Breakpoint(start: 451, end: 800, name: TABLET),
|
const Breakpoint(start: 601, end: 800, name: TABLET),
|
||||||
const Breakpoint(start: 801, end: 1920, name: DESKTOP),
|
const Breakpoint(start: 801, end: 1920, name: DESKTOP),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,13 +42,14 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
_connectCompleter = null;
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectCompleter = Completer<void>();
|
|
||||||
|
|
||||||
if (!_ua.isAuthorized) return;
|
if (!_ua.isAuthorized) return;
|
||||||
if (isConnected || conn != null) {
|
if (isConnected || conn != null) {
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_connectCompleter = Completer<void>();
|
||||||
|
|
||||||
final atk = await _sn.getFreshAtk();
|
final atk = await _sn.getFreshAtk();
|
||||||
final uri = Uri.parse(
|
final uri = Uri.parse(
|
||||||
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
||||||
@@ -57,7 +58,6 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
isBusy = true;
|
isBusy = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
|
||||||
conn = WebSocketChannel.connect(uri);
|
conn = WebSocketChannel.connect(uri);
|
||||||
await conn!.ready;
|
await conn!.ready;
|
||||||
_wsStream = conn!.stream.asBroadcastStream();
|
_wsStream = conn!.stream.asBroadcastStream();
|
||||||
@@ -82,6 +82,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
isBusy = false;
|
isBusy = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_connectCompleter!.complete();
|
_connectCompleter!.complete();
|
||||||
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,10 @@ class _AbuseReportScreenState extends State<AbuseReportScreen> {
|
|||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
if (_isBusy)
|
if (_isBusy)
|
||||||
const CircularProgressIndicator().padding(all: 24).center()
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
).center()
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
|||||||
@@ -45,6 +45,33 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _deletePublisher(SnPublisher publisher) async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'publisherDelete'.tr(args: ['#${publisher.name}']),
|
||||||
|
'publisherDeleteDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await context
|
||||||
|
.read<SnNetworkProvider>()
|
||||||
|
.client
|
||||||
|
.delete('/cgi/co/publishers/${publisher.name}');
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('publisherDeleted'.tr(args: ['#${publisher.name}']));
|
||||||
|
_publishers.remove(publisher);
|
||||||
|
_fetchPublishers();
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -118,6 +145,18 @@ class _PublisherScreenState extends State<PublisherScreen> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.delete),
|
||||||
|
const Gap(16),
|
||||||
|
Text('delete').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_deletePublisher(publisher);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -123,8 +123,10 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
),
|
),
|
||||||
if (_isBusy)
|
if (_isBusy)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child:
|
child: Padding(
|
||||||
const CircularProgressIndicator().padding(all: 24).center(),
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
).center(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
|
|
||||||
SnChannel? _editingChannel;
|
SnChannel? _editingChannel;
|
||||||
|
|
||||||
|
bool _isPublic = false;
|
||||||
|
bool _isCommunity = false;
|
||||||
|
|
||||||
Future<void> _fetchRealms() async {
|
Future<void> _fetchRealms() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
@@ -67,6 +70,8 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
_aliasController.text = _editingChannel!.alias;
|
_aliasController.text = _editingChannel!.alias;
|
||||||
_nameController.text = _editingChannel!.name;
|
_nameController.text = _editingChannel!.name;
|
||||||
_descriptionController.text = _editingChannel!.description;
|
_descriptionController.text = _editingChannel!.description;
|
||||||
|
_isPublic = _editingChannel!.isPublic;
|
||||||
|
_isCommunity = _editingChannel!.isCommunity;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -88,6 +93,8 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
: uuid.v4().replaceAll('-', '').substring(0, 12),
|
: uuid.v4().replaceAll('-', '').substring(0, 12),
|
||||||
'name': _nameController.text,
|
'name': _nameController.text,
|
||||||
'description': _descriptionController.text,
|
'description': _descriptionController.text,
|
||||||
|
'is_public': _isPublic,
|
||||||
|
'is_community': _isCommunity,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -271,6 +278,23 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isPublic,
|
||||||
|
title: Text('channelIsPublic'.tr()),
|
||||||
|
subtitle: Text('channelIsPublicDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isPublic = value ?? false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isCommunity,
|
||||||
|
title: Text('channelIsCommunity'.tr()),
|
||||||
|
subtitle: Text('channelIsCommunityDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isCommunity = value ?? false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class _HomeDashUpdateWidget extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: Card(
|
child: Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Icon(Symbols.update),
|
leading: Icon(Symbols.update),
|
||||||
title: Text('updateAvailable').tr(),
|
title: Text('updateAvailable').tr(),
|
||||||
@@ -180,6 +181,7 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: days.map((ele) {
|
children: days.map((ele) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24),
|
leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24),
|
||||||
title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']),
|
title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']),
|
||||||
@@ -203,6 +205,7 @@ class _HomeDashSpecialDayWidgetState extends State<_HomeDashSpecialDayWidget> {
|
|||||||
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
||||||
final diff = nextOne.$2.difference(DateTime.now());
|
final diff = nextOne.$2.difference(DateTime.now());
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
||||||
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]),
|
title: Text('pending$name').tr(args: [RelativeTime(context).format(date).replaceFirst('in', '').trim()]),
|
||||||
@@ -270,6 +273,7 @@ class _HomeDashTodayNewsState extends State<_HomeDashTodayNews> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -469,6 +473,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -594,6 +599,7 @@ class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -667,11 +673,13 @@ class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendati
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isBusy) {
|
if (_isBusy) {
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: CircularProgressIndicator().center(),
|
child: CircularProgressIndicator().center(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
@@ -110,7 +111,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -136,6 +137,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
builder: (context) => _PostPublisherPopup(
|
builder: (context) => _PostPublisherPopup(
|
||||||
controller: _writeController,
|
controller: _writeController,
|
||||||
publishers: _publishers,
|
publishers: _publishers,
|
||||||
|
onUpdate: () {
|
||||||
|
_fetchPublishers();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -160,7 +164,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_writeController.dispose();
|
_writeController.dispose();
|
||||||
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
hotKeyManager.unregister(_pasteHotKey);
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,6 +256,62 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_writeController.replyingPost != null)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.reply, size: 16),
|
||||||
|
const Gap(10),
|
||||||
|
Text('@${_writeController.replyingPost!.publisher.name}').bold(),
|
||||||
|
const Gap(4),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_writeController.replyingPost!.body['content'],
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_writeController.repostingPost != null)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.forward, size: 16),
|
||||||
|
const Gap(10),
|
||||||
|
Text('@${_writeController.repostingPost!.publisher.name}').bold(),
|
||||||
|
const Gap(4),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_writeController.repostingPost!.body['content'],
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
@@ -434,8 +496,9 @@ class _PostEditorActionScrollBehavior extends MaterialScrollBehavior {
|
|||||||
class _PostPublisherPopup extends StatelessWidget {
|
class _PostPublisherPopup extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
final List<SnPublisher>? publishers;
|
final List<SnPublisher>? publishers;
|
||||||
|
final Function onUpdate;
|
||||||
|
|
||||||
const _PostPublisherPopup({required this.controller, this.publishers});
|
const _PostPublisherPopup({required this.controller, this.publishers, required this.onUpdate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -450,6 +513,20 @@ class _PostPublisherPopup extends StatelessWidget {
|
|||||||
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(),
|
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('publishersNew').tr(),
|
||||||
|
subtitle: Text('publisherNewSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountPublisherNew').then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
onUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: publishers?.length ?? 0,
|
itemCount: publishers?.length ?? 0,
|
||||||
|
|||||||
@@ -186,7 +186,11 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'realmDetail',
|
'realmDetail',
|
||||||
pathParameters: {'alias': realm.alias},
|
pathParameters: {'alias': realm.alias},
|
||||||
);
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
_fetchRealms();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -244,7 +248,11 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'realmDetail',
|
'realmDetail',
|
||||||
pathParameters: {'alias': realm.alias},
|
pathParameters: {'alias': realm.alias},
|
||||||
);
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
_fetchRealms();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
_aliasController.text = out.alias;
|
_aliasController.text = out.alias;
|
||||||
_nameController.text = out.name;
|
_nameController.text = out.name;
|
||||||
_descriptionController.text = out.description;
|
_descriptionController.text = out.description;
|
||||||
|
_isPublic = out.isPublic;
|
||||||
|
_isCommunity = out.isCommunity;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
if (context.mounted) context.showErrorDialog(err);
|
if (context.mounted) context.showErrorDialog(err);
|
||||||
@@ -67,6 +69,9 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
|
|
||||||
final _imagePicker = ImagePicker();
|
final _imagePicker = ImagePicker();
|
||||||
|
|
||||||
|
bool _isPublic = false;
|
||||||
|
bool _isCommunity = false;
|
||||||
|
|
||||||
Future<void> _updateImage(String place) async {
|
Future<void> _updateImage(String place) async {
|
||||||
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
@@ -138,6 +143,8 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
'description': _descriptionController.text,
|
'description': _descriptionController.text,
|
||||||
'avatar': _avatar,
|
'avatar': _avatar,
|
||||||
'banner': _banner,
|
'banner': _banner,
|
||||||
|
'is_public': _isPublic,
|
||||||
|
'is_community': _isCommunity,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -293,6 +300,23 @@ class _RealmManageScreenState extends State<RealmManageScreen> {
|
|||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isPublic,
|
||||||
|
title: Text('realmIsPublic'.tr()),
|
||||||
|
subtitle: Text('realmIsPublicDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isPublic = value ?? false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _isCommunity,
|
||||||
|
title: Text('realmIsCommunity'.tr()),
|
||||||
|
subtitle: Text('realmIsCommunityDescription'.tr()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => _isCommunity = value ?? false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -343,12 +343,31 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}');
|
await sn.client.delete('/cgi/id/realms/${widget.realm!.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _leaveRealm() async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'realmLeave'.tr(),
|
||||||
|
'realmLeaveDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sn.client.delete('/cgi/id/realms/${widget.realm!.alias}/members/me');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
context.showSnackbar('realmDeleted'.tr(args: [
|
|
||||||
'#${widget.realm!.alias}',
|
|
||||||
]));
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -366,6 +385,15 @@ class _RealmSettingsWidgetState extends State<_RealmSettingsWidget> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.logout),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: Text('realmLeave').tr(),
|
||||||
|
subtitle: Text('realmLeaveDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onTap: _isBusy ? null : () => _leaveRealm(),
|
||||||
|
),
|
||||||
|
if (isOwned)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.edit),
|
leading: const Icon(Symbols.edit),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ 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}');
|
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>(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ Future<ThemeData> createAppTheme(
|
|||||||
),
|
),
|
||||||
pageTransitionsTheme: PageTransitionsTheme(
|
pageTransitionsTheme: PageTransitionsTheme(
|
||||||
builders: {
|
builders: {
|
||||||
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
|
TargetPlatform.android: ZoomPageTransitionsBuilder(),
|
||||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||||
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
|
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
|
||||||
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
|
TargetPlatform.fuchsia: ZoomPageTransitionsBuilder(),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class SnAccount with _$SnAccount {
|
|||||||
required String description,
|
required String description,
|
||||||
required String name,
|
required String name,
|
||||||
required String nick,
|
required String nick,
|
||||||
required Map<String, dynamic> permNodes,
|
@Default({}) Map<String, dynamic> permNodes,
|
||||||
required String language,
|
required String language,
|
||||||
required SnAccountProfile? profile,
|
required SnAccountProfile? profile,
|
||||||
@Default([]) List<SnAccountBadge> badges,
|
@Default([]) List<SnAccountBadge> badges,
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
required this.description,
|
required this.description,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.nick,
|
required this.nick,
|
||||||
required final Map<String, dynamic> permNodes,
|
final Map<String, dynamic> permNodes = const {},
|
||||||
required this.language,
|
required this.language,
|
||||||
required this.profile,
|
required this.profile,
|
||||||
final List<SnAccountBadge> badges = const [],
|
final List<SnAccountBadge> badges = const [],
|
||||||
@@ -437,6 +437,7 @@ class _$SnAccountImpl extends _SnAccount {
|
|||||||
final String nick;
|
final String nick;
|
||||||
final Map<String, dynamic> _permNodes;
|
final Map<String, dynamic> _permNodes;
|
||||||
@override
|
@override
|
||||||
|
@JsonKey()
|
||||||
Map<String, dynamic> get permNodes {
|
Map<String, dynamic> get permNodes {
|
||||||
if (_permNodes is EqualUnmodifiableMapView) return _permNodes;
|
if (_permNodes is EqualUnmodifiableMapView) return _permNodes;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
@@ -566,7 +567,7 @@ abstract class _SnAccount extends SnAccount {
|
|||||||
required final String description,
|
required final String description,
|
||||||
required final String name,
|
required final String name,
|
||||||
required final String nick,
|
required final String nick,
|
||||||
required final Map<String, dynamic> permNodes,
|
final Map<String, dynamic> permNodes,
|
||||||
required final String language,
|
required final String language,
|
||||||
required final SnAccountProfile? profile,
|
required final SnAccountProfile? profile,
|
||||||
final List<SnAccountBadge> badges,
|
final List<SnAccountBadge> badges,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ _$SnAccountImpl _$$SnAccountImplFromJson(Map<String, dynamic> json) =>
|
|||||||
description: json['description'] as String,
|
description: json['description'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
nick: json['nick'] as String,
|
nick: json['nick'] as String,
|
||||||
permNodes: json['perm_nodes'] as Map<String, dynamic>,
|
permNodes: json['perm_nodes'] as Map<String, dynamic>? ?? const {},
|
||||||
language: json['language'] as String,
|
language: json['language'] as String,
|
||||||
profile: json['profile'] == null
|
profile: json['profile'] == null
|
||||||
? null
|
? null
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
part 'check_in.freezed.dart';
|
part 'check_in.freezed.dart';
|
||||||
part 'check_in.g.dart';
|
part 'check_in.g.dart';
|
||||||
|
|
||||||
const List<String> kCheckInResultTierSymbols = ['大凶', '凶', '中平', '吉', '大吉'];
|
const List<String> kCheckInResultTierSymbols = ['Bad', 'Poor', 'Medium', 'Good', 'Great'];
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SnCheckInRecord with _$SnCheckInRecord {
|
class SnCheckInRecord with _$SnCheckInRecord {
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ class SnPoll with _$SnPoll {
|
|||||||
required SnPollMetric metric,
|
required SnPollMetric metric,
|
||||||
}) = _SnPoll;
|
}) = _SnPoll;
|
||||||
|
|
||||||
factory SnPoll.fromJson(Map<String, Object?> json) =>
|
factory SnPoll.fromJson(Map<String, Object?> json) => _$SnPollFromJson(json);
|
||||||
_$SnPollFromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -25,11 +24,11 @@ class SnPollMetric with _$SnPollMetric {
|
|||||||
const factory SnPollMetric({
|
const factory SnPollMetric({
|
||||||
required int totalAnswer,
|
required int totalAnswer,
|
||||||
@Default({}) Map<String, int> byOptions,
|
@Default({}) Map<String, int> byOptions,
|
||||||
@Default({}) Map<String, int> byOptionsPercentage,
|
@Default({}) Map<String, double> byOptionsPercentage,
|
||||||
}) = _SnPollMetric;
|
}) = _SnPollMetric;
|
||||||
|
|
||||||
factory SnPollMetric.fromJson(Map<String, Object?> json)
|
factory SnPollMetric.fromJson(Map<String, Object?> json) =>
|
||||||
=> _$SnPollMetricFromJson(json);
|
_$SnPollMetricFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@@ -41,6 +40,6 @@ class SnPollOption with _$SnPollOption {
|
|||||||
required String description,
|
required String description,
|
||||||
}) = _SnPollOption;
|
}) = _SnPollOption;
|
||||||
|
|
||||||
factory SnPollOption.fromJson(Map<String, Object?> json)
|
factory SnPollOption.fromJson(Map<String, Object?> json) =>
|
||||||
=> _$SnPollOptionFromJson(json);
|
_$SnPollOptionFromJson(json);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ SnPollMetric _$SnPollMetricFromJson(Map<String, dynamic> json) {
|
|||||||
mixin _$SnPollMetric {
|
mixin _$SnPollMetric {
|
||||||
int get totalAnswer => throw _privateConstructorUsedError;
|
int get totalAnswer => throw _privateConstructorUsedError;
|
||||||
Map<String, int> get byOptions => throw _privateConstructorUsedError;
|
Map<String, int> get byOptions => throw _privateConstructorUsedError;
|
||||||
Map<String, int> get byOptionsPercentage =>
|
Map<String, double> get byOptionsPercentage =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this SnPollMetric to a JSON map.
|
/// Serializes this SnPollMetric to a JSON map.
|
||||||
@@ -367,7 +367,7 @@ abstract class $SnPollMetricCopyWith<$Res> {
|
|||||||
$Res call(
|
$Res call(
|
||||||
{int totalAnswer,
|
{int totalAnswer,
|
||||||
Map<String, int> byOptions,
|
Map<String, int> byOptions,
|
||||||
Map<String, int> byOptionsPercentage});
|
Map<String, double> byOptionsPercentage});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -401,7 +401,7 @@ class _$SnPollMetricCopyWithImpl<$Res, $Val extends SnPollMetric>
|
|||||||
byOptionsPercentage: null == byOptionsPercentage
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
? _value.byOptionsPercentage
|
? _value.byOptionsPercentage
|
||||||
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, int>,
|
as Map<String, double>,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,7 +417,7 @@ abstract class _$$SnPollMetricImplCopyWith<$Res>
|
|||||||
$Res call(
|
$Res call(
|
||||||
{int totalAnswer,
|
{int totalAnswer,
|
||||||
Map<String, int> byOptions,
|
Map<String, int> byOptions,
|
||||||
Map<String, int> byOptionsPercentage});
|
Map<String, double> byOptionsPercentage});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -449,7 +449,7 @@ class __$$SnPollMetricImplCopyWithImpl<$Res>
|
|||||||
byOptionsPercentage: null == byOptionsPercentage
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
? _value._byOptionsPercentage
|
? _value._byOptionsPercentage
|
||||||
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
as Map<String, int>,
|
as Map<String, double>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,7 +460,7 @@ class _$SnPollMetricImpl implements _SnPollMetric {
|
|||||||
const _$SnPollMetricImpl(
|
const _$SnPollMetricImpl(
|
||||||
{required this.totalAnswer,
|
{required this.totalAnswer,
|
||||||
final Map<String, int> byOptions = const {},
|
final Map<String, int> byOptions = const {},
|
||||||
final Map<String, int> byOptionsPercentage = const {}})
|
final Map<String, double> byOptionsPercentage = const {}})
|
||||||
: _byOptions = byOptions,
|
: _byOptions = byOptions,
|
||||||
_byOptionsPercentage = byOptionsPercentage;
|
_byOptionsPercentage = byOptionsPercentage;
|
||||||
|
|
||||||
@@ -478,10 +478,10 @@ class _$SnPollMetricImpl implements _SnPollMetric {
|
|||||||
return EqualUnmodifiableMapView(_byOptions);
|
return EqualUnmodifiableMapView(_byOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, int> _byOptionsPercentage;
|
final Map<String, double> _byOptionsPercentage;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
Map<String, int> get byOptionsPercentage {
|
Map<String, double> get byOptionsPercentage {
|
||||||
if (_byOptionsPercentage is EqualUnmodifiableMapView)
|
if (_byOptionsPercentage is EqualUnmodifiableMapView)
|
||||||
return _byOptionsPercentage;
|
return _byOptionsPercentage;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
@@ -534,7 +534,7 @@ abstract class _SnPollMetric implements SnPollMetric {
|
|||||||
const factory _SnPollMetric(
|
const factory _SnPollMetric(
|
||||||
{required final int totalAnswer,
|
{required final int totalAnswer,
|
||||||
final Map<String, int> byOptions,
|
final Map<String, int> byOptions,
|
||||||
final Map<String, int> byOptionsPercentage}) = _$SnPollMetricImpl;
|
final Map<String, double> byOptionsPercentage}) = _$SnPollMetricImpl;
|
||||||
|
|
||||||
factory _SnPollMetric.fromJson(Map<String, dynamic> json) =
|
factory _SnPollMetric.fromJson(Map<String, dynamic> json) =
|
||||||
_$SnPollMetricImpl.fromJson;
|
_$SnPollMetricImpl.fromJson;
|
||||||
@@ -544,7 +544,7 @@ abstract class _SnPollMetric implements SnPollMetric {
|
|||||||
@override
|
@override
|
||||||
Map<String, int> get byOptions;
|
Map<String, int> get byOptions;
|
||||||
@override
|
@override
|
||||||
Map<String, int> get byOptionsPercentage;
|
Map<String, double> get byOptionsPercentage;
|
||||||
|
|
||||||
/// Create a copy of SnPollMetric
|
/// Create a copy of SnPollMetric
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ _$SnPollMetricImpl _$$SnPollMetricImplFromJson(Map<String, dynamic> json) =>
|
|||||||
const {},
|
const {},
|
||||||
byOptionsPercentage:
|
byOptionsPercentage:
|
||||||
(json['by_options_percentage'] as Map<String, dynamic>?)?.map(
|
(json['by_options_percentage'] as Map<String, dynamic>?)?.map(
|
||||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
(k, e) => MapEntry(k, (e as num).toDouble()),
|
||||||
) ??
|
) ??
|
||||||
const {},
|
const {},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,7 +45,12 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
);
|
||||||
|
final HotKey _newLineHotKey = HotKey(
|
||||||
|
key: PhysicalKeyboardKey.enter,
|
||||||
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -61,6 +66,10 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
));
|
));
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
|
hotKeyManager.register(_newLineHotKey, keyDownHandler: (_) async {
|
||||||
|
if (_contentController.text.isEmpty) return;
|
||||||
|
_contentController.text += '\n';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -112,6 +121,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _sendMessage() async {
|
Future<void> _sendMessage() async {
|
||||||
|
if (_contentController.text.isEmpty && _attachments.isEmpty) return;
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
|
|
||||||
final attach = context.read<SnAttachmentProvider>();
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
@@ -204,7 +214,10 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
_contentController.dispose();
|
_contentController.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
_dismissEmojiPicker();
|
_dismissEmojiPicker();
|
||||||
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
hotKeyManager.unregister(_pasteHotKey);
|
||||||
|
hotKeyManager.unregister(_newLineHotKey);
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +357,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
_sendMessage();
|
_sendMessage();
|
||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
},
|
},
|
||||||
|
maxLines: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
|||||||
@@ -166,10 +166,12 @@ class AppRootScaffold extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
|
mainAxisAlignment: Platform.isMacOS ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
|
child: Text(
|
||||||
'Solar Network',
|
'Solar Network',
|
||||||
style: GoogleFonts.spaceGrotesk(),
|
style: GoogleFonts.spaceGrotesk(),
|
||||||
).padding(horizontal: 12, vertical: 5),
|
).padding(horizontal: 12, vertical: 5),
|
||||||
|
),
|
||||||
if (!Platform.isMacOS)
|
if (!Platform.isMacOS)
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -965,7 +965,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postEditor',
|
'postEditor',
|
||||||
pathParameters: {'mode': data.typePlural},
|
pathParameters: {'mode': 'stories'},
|
||||||
queryParameters: {'replying': data.id.toString()},
|
queryParameters: {'replying': data.id.toString()},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -981,7 +981,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'postEditor',
|
'postEditor',
|
||||||
pathParameters: {'mode': data.typePlural},
|
pathParameters: {'mode': 'stories'},
|
||||||
queryParameters: {'reposting': data.id.toString()},
|
queryParameters: {'reposting': data.id.toString()},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,19 +31,27 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
|
|
||||||
String? _answeredChoice;
|
String? _answeredChoice;
|
||||||
|
|
||||||
|
Future<void> _refreshPoll() async {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/polls/${widget.poll.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _poll = SnPoll.fromJson(resp.data!));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchAnswer() async {
|
Future<void> _fetchAnswer() async {
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
if (!ua.isAuthorized) return;
|
if (!ua.isAuthorized) return;
|
||||||
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/co/polls/${widget.poll.id}/answer');
|
final resp =
|
||||||
|
await sn.client.get('/cgi/co/polls/${widget.poll.id}/answer');
|
||||||
_answeredChoice = resp.data?['answer'];
|
_answeredChoice = resp.data?['answer'];
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
// ignore because it may not found
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
@@ -59,8 +67,9 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
'answer': option.id,
|
'answer': option.id,
|
||||||
});
|
});
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showSnackbar('pollAnswered'.tr());
|
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
|
_answeredChoice = option.id;
|
||||||
|
_refreshPoll();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@@ -78,15 +87,24 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
for (final option in _poll.options)
|
for (final option in _poll.options)
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
height: 60,
|
height: 60,
|
||||||
width: MediaQuery.of(context).size.width * (_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble(),
|
width: MediaQuery.of(context).size.width *
|
||||||
|
(_poll.metric.byOptionsPercentage[option.id] ?? 0)
|
||||||
|
.toDouble(),
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
minTileHeight: 60,
|
minTileHeight: 60,
|
||||||
leading: _answeredChoice == option.id ? const Icon(Symbols.circle, fill: 1) : const Icon(Symbols.circle),
|
leading: _answeredChoice == option.id
|
||||||
|
? const Icon(Symbols.circle, fill: 1)
|
||||||
|
: const Icon(Symbols.circle),
|
||||||
title: Text(option.name),
|
title: Text(option.name),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -95,14 +113,18 @@ class _PostPollState extends State<PostPoll> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text('pollVotes'.plural(_poll.metric.byOptions[option.id] ?? 0)),
|
Text(
|
||||||
|
'pollVotes'
|
||||||
|
.plural(_poll.metric.byOptions[option.id] ?? 0),
|
||||||
|
),
|
||||||
Text(' · ').padding(horizontal: 4),
|
Text(' · ').padding(horizontal: 4),
|
||||||
Text(
|
Text(
|
||||||
'${((_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble() * 100).toStringAsFixed(2)}%',
|
'${((_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble() * 100).toStringAsFixed(2)}%',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (option.description.isNotEmpty) Text(option.description),
|
if (option.description.isNotEmpty)
|
||||||
|
Text(option.description),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: _isBusy ? null : () => _voteForOption(option),
|
onTap: _isBusy ? null : () => _voteForOption(option),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "my_application.h"
|
|
||||||
|
|
||||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
#include <flutter_linux/flutter_linux.h>
|
#include <flutter_linux/flutter_linux.h>
|
||||||
#ifdef GDK_WINDOWING_X11
|
#ifdef GDK_WINDOWING_X11
|
||||||
@@ -42,15 +41,16 @@ static void my_application_activate(GApplication* application) {
|
|||||||
if (use_header_bar) {
|
if (use_header_bar) {
|
||||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
gtk_header_bar_set_title(header_bar, "Surface");
|
gtk_header_bar_set_title(header_bar, "bitsdojo_window_example");
|
||||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
} else {
|
} else {
|
||||||
gtk_window_set_title(window, "Surface");
|
gtk_window_set_title(window, "bitsdojo_window_example");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bdw = bitsdojo_window_from(window);
|
auto bdw = bitsdojo_window_from(window);
|
||||||
bdw->setCustomFrame(true);
|
bdw->setCustomFrame(true);
|
||||||
|
//gtk_window_set_default_size(window, 1280, 720);
|
||||||
gtk_widget_show(GTK_WIDGET(window));
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
@@ -84,24 +84,6 @@ static gboolean my_application_local_command_line(GApplication* application, gch
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements GApplication::startup.
|
|
||||||
static void my_application_startup(GApplication* application) {
|
|
||||||
//MyApplication* self = MY_APPLICATION(object);
|
|
||||||
|
|
||||||
// Perform any actions required at application startup.
|
|
||||||
|
|
||||||
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements GApplication::shutdown.
|
|
||||||
static void my_application_shutdown(GApplication* application) {
|
|
||||||
//MyApplication* self = MY_APPLICATION(object);
|
|
||||||
|
|
||||||
// Perform any actions required at application shutdown.
|
|
||||||
|
|
||||||
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements GObject::dispose.
|
// Implements GObject::dispose.
|
||||||
static void my_application_dispose(GObject* object) {
|
static void my_application_dispose(GObject* object) {
|
||||||
MyApplication* self = MY_APPLICATION(object);
|
MyApplication* self = MY_APPLICATION(object);
|
||||||
@@ -112,8 +94,6 @@ static void my_application_dispose(GObject* object) {
|
|||||||
static void my_application_class_init(MyApplicationClass* klass) {
|
static void my_application_class_init(MyApplicationClass* klass) {
|
||||||
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||||
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
|
||||||
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
|
||||||
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
pubspec.lock
14
pubspec.lock
@@ -354,10 +354,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dart_webrtc
|
name: dart_webrtc
|
||||||
sha256: "3b3ff59c66cbc1577ed0f28d7005b5163555208fb1697a42207424ab8baa27c5"
|
sha256: "03df5b41b23bc185ebcf4b0ffc92d002e295bf56287fb5f9d2c321ddaf7760cc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.1"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -498,10 +498,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "3d57312a53746ed4eb8c843dc50372454bbda37dd0c01a4d40fedc83e2ce4921"
|
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.5"
|
version: "8.3.7"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -538,10 +538,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_windows
|
name: file_selector_windows
|
||||||
sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4"
|
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+3"
|
version: "0.9.3+4"
|
||||||
firebase_analytics:
|
firebase_analytics:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1043,7 +1043,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
|
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
|
||||||
|
|||||||
@@ -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.3.2+67
|
version: 2.3.2+69
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
@@ -120,6 +120,7 @@ dependencies:
|
|||||||
xml: ^6.5.0
|
xml: ^6.5.0
|
||||||
tray_manager: ^0.3.2
|
tray_manager: ^0.3.2
|
||||||
hotkey_manager: ^0.2.3
|
hotkey_manager: ^0.2.3
|
||||||
|
image_picker_android: ^0.8.12+20
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user