Skip to content

Commit 9c25d63

Browse files
authored
Merge pull request #4 from AndroidIRCx/develop
Advance sprint 4 chat parity and message UX
2 parents 6823c84 + 3d920e1 commit 9c25d63

35 files changed

Lines changed: 6625 additions & 144 deletions
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Release Android
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: write
8+
9+
jobs:
10+
build-android:
11+
name: Build Android APK
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Set up Java
21+
uses: actions/setup-java@v4
22+
with:
23+
distribution: temurin
24+
java-version: '17'
25+
26+
- name: Setup Flutter
27+
uses: subosito/flutter-action@v2
28+
with:
29+
channel: stable
30+
cache: true
31+
32+
- name: Install dependencies
33+
run: flutter pub get
34+
35+
- name: Build release APK
36+
run: flutter build apk --release
37+
38+
- name: Prepare APK artifact
39+
shell: bash
40+
run: |
41+
mkdir -p release-assets
42+
cp build/app/outputs/flutter-apk/app-release.apk release-assets/AndroidIRCx-Flutter-android.apk
43+
44+
- name: Upload workflow artifact
45+
uses: actions/upload-artifact@v4
46+
with:
47+
name: android-release-apk
48+
path: release-assets/AndroidIRCx-Flutter-android.apk
49+
50+
- name: Attach APK to GitHub release
51+
if: startsWith(github.ref, 'refs/tags/')
52+
uses: softprops/action-gh-release@v2
53+
with:
54+
tag_name: ${{ github.ref_name }}
55+
generate_release_notes: true
56+
files: release-assets/AndroidIRCx-Flutter-android.apk
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Release Windows
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: write
8+
9+
jobs:
10+
build-windows:
11+
name: Build Windows Package
12+
runs-on: windows-latest
13+
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Setup Flutter
21+
uses: subosito/flutter-action@v2
22+
with:
23+
channel: stable
24+
cache: true
25+
26+
- name: Install dependencies
27+
run: flutter pub get
28+
29+
- name: Enable Windows desktop
30+
run: flutter config --enable-windows-desktop
31+
32+
- name: Build Windows release
33+
run: flutter build windows --release
34+
35+
- name: Package Windows release
36+
shell: pwsh
37+
run: |
38+
$artifactDir = "release-assets"
39+
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
40+
$source = "build/windows/x64/runner/Release"
41+
$zipPath = Join-Path $artifactDir "AndroidIRCx-Flutter-windows.zip"
42+
if (Test-Path $zipPath) {
43+
Remove-Item $zipPath -Force
44+
}
45+
Compress-Archive -Path "$source/*" -DestinationPath $zipPath
46+
47+
- name: Upload workflow artifact
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: windows-release-zip
51+
path: release-assets/AndroidIRCx-Flutter-windows.zip
52+
53+
- name: Attach Windows package to GitHub release
54+
if: startsWith(github.ref, 'refs/tags/')
55+
uses: softprops/action-gh-release@v2
56+
with:
57+
tag_name: ${{ github.ref_name }}
58+
generate_release_notes: true
59+
files: release-assets/AndroidIRCx-Flutter-windows.zip

devtools_options.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
description: This file stores settings for Dart & Flutter DevTools.
2+
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3+
extensions:
4+
- shared_preferences: true

lib/core/models/app_settings.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,49 @@
1+
enum NoticeRoutingMode { server, active, notice, private }
2+
13
class AppSettings {
24
const AppSettings({
35
this.showRawEvents = true,
6+
this.noticeRouting = NoticeRoutingMode.server,
7+
this.showHeaderSearchButton = true,
8+
this.showAttachmentPreviews = true,
49
});
510

611
final bool showRawEvents;
12+
final NoticeRoutingMode noticeRouting;
13+
final bool showHeaderSearchButton;
14+
final bool showAttachmentPreviews;
715

816
AppSettings copyWith({
917
bool? showRawEvents,
18+
NoticeRoutingMode? noticeRouting,
19+
bool? showHeaderSearchButton,
20+
bool? showAttachmentPreviews,
1021
}) {
1122
return AppSettings(
1223
showRawEvents: showRawEvents ?? this.showRawEvents,
24+
noticeRouting: noticeRouting ?? this.noticeRouting,
25+
showHeaderSearchButton: showHeaderSearchButton ?? this.showHeaderSearchButton,
26+
showAttachmentPreviews: showAttachmentPreviews ?? this.showAttachmentPreviews,
1327
);
1428
}
1529

1630
Map<String, Object?> toJson() {
1731
return {
1832
'showRawEvents': showRawEvents,
33+
'noticeRouting': noticeRouting.name,
34+
'showHeaderSearchButton': showHeaderSearchButton,
35+
'showAttachmentPreviews': showAttachmentPreviews,
1936
};
2037
}
2138

2239
factory AppSettings.fromJson(Map<String, Object?> json) {
2340
return AppSettings(
2441
showRawEvents: (json['showRawEvents'] as bool?) ?? true,
42+
noticeRouting: json['noticeRouting'] is String
43+
? NoticeRoutingMode.values.byName(json['noticeRouting']! as String)
44+
: NoticeRoutingMode.server,
45+
showHeaderSearchButton: (json['showHeaderSearchButton'] as bool?) ?? true,
46+
showAttachmentPreviews: (json['showAttachmentPreviews'] as bool?) ?? true,
2547
);
2648
}
2749
}

lib/core/models/chat_tab.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
enum ChatTabType { server, channel, query, notice }
1+
enum ChatTabType { server, channel, query, notice, dcc }
22

33
class ChatTab {
44
const ChatTab({

lib/core/models/dcc_session.dart

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
enum DccSessionType { chat, send, unknown }
2+
3+
enum DccSessionStatus { pending, offering, connecting, connected, closed, failed }
4+
5+
class DccSession {
6+
const DccSession({
7+
required this.id,
8+
required this.tabId,
9+
required this.peerNick,
10+
required this.type,
11+
required this.status,
12+
required this.direction,
13+
this.filename,
14+
this.host,
15+
this.port,
16+
this.size,
17+
this.token,
18+
this.filePath,
19+
this.bytesTransferred = 0,
20+
this.error,
21+
});
22+
23+
final String id;
24+
final String tabId;
25+
final String peerNick;
26+
final DccSessionType type;
27+
final DccSessionStatus status;
28+
final String direction;
29+
final String? filename;
30+
final String? host;
31+
final int? port;
32+
final int? size;
33+
final String? token;
34+
final String? filePath;
35+
final int bytesTransferred;
36+
final String? error;
37+
38+
DccSession copyWith({
39+
String? id,
40+
String? tabId,
41+
String? peerNick,
42+
DccSessionType? type,
43+
DccSessionStatus? status,
44+
String? direction,
45+
String? filename,
46+
String? host,
47+
int? port,
48+
int? size,
49+
String? token,
50+
String? filePath,
51+
int? bytesTransferred,
52+
String? error,
53+
}) {
54+
return DccSession(
55+
id: id ?? this.id,
56+
tabId: tabId ?? this.tabId,
57+
peerNick: peerNick ?? this.peerNick,
58+
type: type ?? this.type,
59+
status: status ?? this.status,
60+
direction: direction ?? this.direction,
61+
filename: filename ?? this.filename,
62+
host: host ?? this.host,
63+
port: port ?? this.port,
64+
size: size ?? this.size,
65+
token: token ?? this.token,
66+
filePath: filePath ?? this.filePath,
67+
bytesTransferred: bytesTransferred ?? this.bytesTransferred,
68+
error: error ?? this.error,
69+
);
70+
}
71+
}

lib/core/models/irc_message.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class IrcMessage {
77
required this.sender,
88
required this.content,
99
required this.timestamp,
10+
this.tags = const <String, String?>{},
11+
this.isPlayback = false,
1012
this.isOwn = false,
1113
this.kind = IrcMessageKind.chat,
1214
});
@@ -16,6 +18,8 @@ class IrcMessage {
1618
final String sender;
1719
final String content;
1820
final DateTime timestamp;
21+
final Map<String, String?> tags;
22+
final bool isPlayback;
1923
final bool isOwn;
2024
final IrcMessageKind kind;
2125

@@ -26,6 +30,8 @@ class IrcMessage {
2630
'sender': sender,
2731
'content': content,
2832
'timestamp': timestamp.toIso8601String(),
33+
'tags': tags,
34+
'isPlayback': isPlayback,
2935
'isOwn': isOwn,
3036
'kind': kind.name,
3137
};
@@ -38,6 +44,8 @@ class IrcMessage {
3844
sender: json['sender']! as String,
3945
content: json['content']! as String,
4046
timestamp: DateTime.parse(json['timestamp']! as String),
47+
tags: Map<String, String?>.from((json['tags'] as Map?) ?? const <String, String?>{}),
48+
isPlayback: (json['isPlayback'] as bool?) ?? false,
4149
isOwn: (json['isOwn'] as bool?) ?? false,
4250
kind: IrcMessageKind.values.byName(
4351
(json['kind'] as String?) ?? IrcMessageKind.chat.name,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'dcc_file_store_stub.dart' if (dart.library.io) 'dcc_file_store_io.dart';
2+
3+
abstract class DccFileSink {
4+
void add(List<int> bytes);
5+
6+
Future<void> flush();
7+
8+
Future<void> close();
9+
}
10+
11+
typedef DccTempFile = ({String path, DccFileSink sink});
12+
typedef DccSourceFile = ({
13+
String path,
14+
String fileName,
15+
int size,
16+
Future<List<int>> Function() readAllBytes,
17+
});
18+
19+
Future<DccTempFile> createDccTempFile(String fileName) => createPlatformDccTempFile(fileName);
20+
21+
Future<DccSourceFile> openDccSourceFile(String path) => openPlatformDccSourceFile(path);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'dart:io';
2+
3+
import 'dcc_file_store.dart';
4+
5+
class _IoDccFileSink implements DccFileSink {
6+
_IoDccFileSink(this._sink);
7+
8+
final IOSink _sink;
9+
10+
@override
11+
void add(List<int> bytes) {
12+
_sink.add(bytes);
13+
}
14+
15+
@override
16+
Future<void> close() => _sink.close();
17+
18+
@override
19+
Future<void> flush() => _sink.flush();
20+
}
21+
22+
Future<DccTempFile> createPlatformDccTempFile(String fileName) async {
23+
final sanitized = fileName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_');
24+
final path = '${Directory.systemTemp.path}/$sanitized';
25+
final file = File(path);
26+
final sink = file.openWrite(mode: FileMode.writeOnly);
27+
return (path: path, sink: _IoDccFileSink(sink));
28+
}
29+
30+
Future<DccSourceFile> openPlatformDccSourceFile(String path) async {
31+
final file = File(path);
32+
final exists = await file.exists();
33+
if (!exists) {
34+
throw FileSystemException('DCC source file does not exist.', path);
35+
}
36+
37+
final stat = await file.stat();
38+
return (
39+
path: file.path,
40+
fileName: file.uri.pathSegments.isEmpty ? file.path : file.uri.pathSegments.last,
41+
size: stat.size,
42+
readAllBytes: file.readAsBytes,
43+
);
44+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'dcc_file_store.dart';
2+
3+
Future<DccTempFile> createPlatformDccTempFile(String fileName) async {
4+
throw UnsupportedError('DCC file storage is only supported on IO platforms.');
5+
}
6+
7+
Future<DccSourceFile> openPlatformDccSourceFile(String path) async {
8+
throw UnsupportedError('Outgoing DCC SEND is only supported on IO platforms.');
9+
}

0 commit comments

Comments
 (0)