Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add video zoom functionality in lightbox #1302

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,15 @@ try {

android {
namespace "com.zulip.flutter"

compileSdkVersion flutter.compileSdkVersion

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '11'
}

sourceSets {
Expand Down
1 change: 1 addition & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ agpVersion=8.5.2
# A helpful discussion is at:
# https://stackoverflow.com/a/74425347
kotlinVersion=2.0.10
kotlin.jvm.target=11
29 changes: 16 additions & 13 deletions lib/widgets/lightbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ class VideoLightboxPage extends StatefulWidget {

class _VideoLightboxPageState extends State<VideoLightboxPage> with PerAccountStoreAwareStateMixin<VideoLightboxPage> {
VideoPlayerController? _controller;
final TransformationController _transformationController = TransformationController();

@override
void onNewStore() {
Expand Down Expand Up @@ -494,6 +495,7 @@ class _VideoLightboxPageState extends State<VideoLightboxPage> with PerAccountSt
_controller?.removeListener(_handleVideoControllerUpdate);
_controller?.dispose();
_controller = null;
_transformationController.dispose();
// The VideoController doesn't emit a pause event
// while disposing, so disable the wakelock here
// explicitly.
Expand Down Expand Up @@ -546,21 +548,22 @@ class _VideoLightboxPageState extends State<VideoLightboxPage> with PerAccountSt
return _LightboxPageLayout(
routeEntranceAnimation: widget.routeEntranceAnimation,
message: widget.message,
buildAppBarBottom: (context) => null,
buildAppBarBottom: (BuildContext context) => null,
buildBottomAppBar: _buildBottomAppBar,
child: SafeArea(
child: Center(
child: Stack(alignment: Alignment.center, children: [
if (_controller != null && _controller!.value.isInitialized)
AspectRatio(
child: _controller == null
? const Center(child: CircularProgressIndicator())
: Center(
child: InteractiveViewer(
transformationController: _transformationController,
minScale: 1.0,
maxScale: 5.0,
child: AspectRatio(
aspectRatio: _controller!.value.aspectRatio,
child: VideoPlayer(_controller!)),
if (_controller == null || !_controller!.value.isInitialized || _controller!.value.isBuffering)
const SizedBox(
width: 32,
height: 32,
child: CircularProgressIndicator(color: Colors.white)),
]))));
child: VideoPlayer(_controller!),
),
),
),
);
}
}

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#Sun Jan 26 10:24:06 IST 2025
gradle.version=8.10
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
F9DBEE8947D96C7624685DFC098354BE5ED14573C4884BAC8865EBDD16FEE696
Empty file.
56 changes: 28 additions & 28 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.3.0"
charcode:
dependency: transitive
description:
Expand Down Expand Up @@ -186,10 +186,10 @@ packages:
dependency: "direct main"
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.19.1"
version: "1.19.0"
color_models:
dependency: "direct overridden"
description:
Expand Down Expand Up @@ -703,10 +703,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
Expand All @@ -719,10 +719,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.15.0"
mime:
dependency: "direct main"
description:
Expand Down Expand Up @@ -831,10 +831,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.1.0"
version: "6.0.2"
pigeon:
dependency: "direct dev"
description:
Expand Down Expand Up @@ -996,10 +996,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
version: "1.10.0"
sprintf:
dependency: transitive
description:
Expand Down Expand Up @@ -1036,18 +1036,18 @@ packages:
dependency: "direct dev"
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
version: "1.12.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "4ac0537115a24d772c408a2520ecd0abb99bca2ea9c4e634ccbdbfae64fe17ec"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.2"
stream_transform:
dependency: transitive
description:
Expand All @@ -1060,10 +1060,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.4.0"
sync_http:
dependency: transitive
description:
Expand All @@ -1076,34 +1076,34 @@ packages:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.2"
version: "1.2.1"
test:
dependency: "direct dev"
description:
name: test
sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d"
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
url: "https://pub.dev"
source: hosted
version: "1.25.14"
version: "1.25.8"
test_api:
dependency: "direct dev"
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.4"
version: "0.7.3"
test_core:
dependency: transitive
description:
name: test_core
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
url: "https://pub.dev"
source: hosted
version: "0.6.8"
version: "0.6.5"
timing:
dependency: transitive
description:
Expand Down Expand Up @@ -1360,5 +1360,5 @@ packages:
source: path
version: "0.0.1"
sdks:
dart: ">=3.7.0-312.0.dev <4.0.0"
flutter: ">=3.28.0-2.0.pre.38699"
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.0"
72 changes: 72 additions & 0 deletions test/widgets/lightbox_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -557,5 +557,77 @@ void main() {
check(position).isGreaterThan(basePosition);
check(platform.position).equals(position);
});

testWidgets('video zoom functionality', (tester) async {
await setupPage(tester, videoSrc: Uri.parse(kTestVideoUrl));

// Find the InteractiveViewer that wraps the VideoPlayer
final interactiveViewer = tester.widget<InteractiveViewer>(
find.ancestor(
of: find.byType(VideoPlayer),
matching: find.byType(InteractiveViewer),
),
);

// Simulate pinch-to-zoom gesture
final center = tester.getCenter(find.byType(VideoPlayer));
await tester.startGesture(center, kind: PointerDeviceKind.touch);
await tester.startGesture(center + const Offset(50.0, 0.0), kind: PointerDeviceKind.touch);
await tester.pump();

// Check if the InteractiveViewer allows zoom
check(interactiveViewer.maxScale).isGreaterThan(1.0);
});

testWidgets('video pan functionality', (tester) async {
await setupPage(tester, videoSrc: Uri.parse(kTestVideoUrl));

// Find the InteractiveViewer
final interactiveViewer = tester.widget<InteractiveViewer>(
find.ancestor(
of: find.byType(VideoPlayer),
matching: find.byType(InteractiveViewer),
),
);

// Check if pan is enabled
check(interactiveViewer.panEnabled).isTrue();

// Simulate pan gesture
await tester.drag(find.byType(InteractiveViewer), const Offset(100, 0));
await tester.pump();
});

testWidgets('maintain aspect ratio during zoom', (tester) async {
await setupPage(tester, videoSrc: Uri.parse(kTestVideoUrl));

// Find the VideoPlayer
final videoPlayer = tester.widget<VideoPlayer>(find.byType(VideoPlayer));
final aspectRatio = videoPlayer.controller?.value.aspectRatio ?? 1.0;

// Find the InteractiveViewer
final interactiveViewer = tester.widget<InteractiveViewer>(
find.ancestor(
of: find.byType(VideoPlayer),
matching: find.byType(InteractiveViewer),
),
);

// Check if the InteractiveViewer preserves aspect ratio
check(interactiveViewer.constrained).isTrue();
check(aspectRatio).isGreaterThan(0.0);
});
});

Widget _loadingBuilder(BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;

return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
);
}
}
Loading