Skip to content
Merged
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
23 changes: 18 additions & 5 deletions .github/workflows/manual-prepare_release_branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,27 @@ jobs:

done

echo "→ Bumping Android native SDK in build.gradle"
echo " Before:" && grep "cloud.mindbox:mobile-sdk" mindbox_android/android/build.gradle || true
sed -i "s/cloud.mindbox:mobile-sdk:.*/cloud.mindbox:mobile-sdk:$AND_VER'/" mindbox_android/android/build.gradle
echo " After:" && grep "cloud.mindbox:mobile-sdk" mindbox_android/android/build.gradle || true

# Fail the release if a version substitution didn't land (stale pin).
assert_pin() { grep -qE "$2" "$1" || { echo "ERROR: pattern /$2/ not found in $1 — substitution failed"; exit 1; }; }

echo "→ Bumping Android native SDK in build.gradle"
echo " Before:" && grep "cloud.mindbox:" mindbox_android/android/build.gradle || true
# Every cloud.mindbox artifact in this file ships from the same native release, so they all
# take the same version — the ones named today and any added after this was written.
sed -i -E "s/(cloud\.mindbox:[a-z0-9-]+:)[^']*'/\1$AND_VER'/" mindbox_android/android/build.gradle
echo " After:" && grep "cloud.mindbox:" mindbox_android/android/build.gradle || true

# Named artifacts are asserted the way the iOS pins are; the sweep after them catches a
# dependency nobody thought to name here, which would otherwise ship stale without a word.
assert_pin mindbox_android/android/build.gradle "cloud\.mindbox:mobile-sdk:$AND_VER'"
assert_pin mindbox_android/android/build.gradle "cloud\.mindbox:mindbox-common:$AND_VER'"
STALE=$(grep -nE "cloud\.mindbox:[a-z0-9-]+:" mindbox_android/android/build.gradle | grep -v ":$AND_VER'" || true)
if [ -n "$STALE" ]; then
echo "ERROR: build.gradle still pins Mindbox artifacts to another version:"
echo "$STALE"
exit 1
fi

echo "→ Bumping iOS native SDK in podspec"
echo " Before s.version:" && grep -E "s\.version" mindbox_ios/ios/mindbox_ios.podspec || true
sed -i -E "s/(s\.version *= *')[^']+(')/\1$IO_VER\2/" mindbox_ios/ios/mindbox_ios.podspec
Expand Down
21 changes: 16 additions & 5 deletions git-release-branch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,28 @@ if ! [[ $ios_sdk_version =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc)?$ ]]; then
exit 1
fi

android_gradle="mindbox_android/android/build.gradle"
sed -i '' "s/ api 'cloud.mindbox:mobile-sdk:.*/ api 'cloud.mindbox:mobile-sdk:$android_sdk_version\'/" $android_gradle

echo "Bump $android_gradle to $android_sdk_version"

# Fail loudly if a version substitution didn't land (e.g. the line format
# changed and sed silently matched nothing, leaving a stale pin).
assert_pin() { # <file> <grep-ERE>
grep -qE "$2" "$1" || { echo "ERROR: pattern /$2/ not found in $1 — version substitution failed"; exit 1; }
}

android_gradle="mindbox_android/android/build.gradle"
# Every cloud.mindbox artifact in this file ships from the same native release, so they all
# take the same version — the ones named today and any added after this was written.
sed -i '' -E "s/(cloud\.mindbox:[a-z0-9-]+:)[^']*'/\1$android_sdk_version'/" $android_gradle

echo "Bump $android_gradle to $android_sdk_version"

assert_pin $android_gradle "cloud\.mindbox:mobile-sdk:$android_sdk_version'"
assert_pin $android_gradle "cloud\.mindbox:mindbox-common:$android_sdk_version'"
stale_pins=$(grep -nE "cloud\.mindbox:[a-z0-9-]+:" $android_gradle | grep -v ":$android_sdk_version'" || true)
if [ -n "$stale_pins" ]; then
echo "ERROR: $android_gradle still pins Mindbox artifacts to another version:"
echo "$stale_pins"
exit 1
fi

ios_podspec="mindbox_ios/ios/mindbox_ios.podspec"

sed -i '' "s/ s.version = .*/ s.version = '$ios_sdk_version'/" $ios_podspec
Expand Down
15 changes: 14 additions & 1 deletion mindbox/lib/src/embedded_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,24 @@ class _EmbeddedBlockState extends State<_EmbeddedBlock> {
creationParams[EmbeddedBlockParams.timeoutMs] = timeout.inMilliseconds;
}

// The recognizer is built by hand rather than by a RawGestureDetector, so nothing hands it the
// touch slop of the device the way the framework hands it to every scrollable. Left to itself it
// falls back to kTouchSlop — 18 logical pixels against the 8 an Android scrollable plays with —
// and a scrollable that wants the same direction takes the drag while the block is still short
// of its own threshold: the arena closes, and the carousel is never told a finger was on it. On
// equal slop the drag goes to whoever is closest to the finger, and inside the block that is the
// block.
//
// Read whole rather than by aspect: the aspect accessors arrived in Flutter 3.10, and the plugin
// still speaks to 3.0. Watching all of MediaQuery costs nothing here — a platform view keeps the
// recognizer it was first given, so the settings are read once however they are asked for.
final DeviceGestureSettings? gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;

final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers =
_appearance == EmbeddedBlockAppearance.content
? <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => HorizontalDragGestureRecognizer(),
() => HorizontalDragGestureRecognizer()..gestureSettings = gestureSettings,
),
}
: const <Factory<OneSequenceGestureRecognizer>>{};
Expand Down
4 changes: 2 additions & 2 deletions mindbox/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox
documentation: https://developers.mindbox.ru/docs/flutter-sdk-integration

environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
sdk: ">=2.17.0 <4.0.0"
flutter: ">=3.0.0"

flutter:
plugin:
Expand Down
161 changes: 161 additions & 0 deletions mindbox/test/embedded_block_test.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
Expand Down Expand Up @@ -482,4 +483,164 @@ void main() {
expect(methods, isNot(contains(EmbeddedBlockMethods.release)));
});
});

group('A drag that two scrollables want', () {
// The device slop an Android scrollable plays with. The block used to fall back to kTouchSlop —
// 18 — and lose every horizontal drag to a parent that crosses 8 first.
const DeviceGestureSettings settings = DeviceGestureSettings(touchSlop: 8);

late int viewId;

setUp(() {
viewId = -1;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform_views, (MethodCall call) async {
// 'touch' carries a list, not a map: the block wins the arena and forwards the drag, so
// this handler is asked about more than the two methods it answers.
if (call.method != 'create' && call.method != 'resize') {
return null;
}

final Map<Object?, Object?> arguments = call.arguments as Map<Object?, Object?>;

if (call.method == 'resize') {
return <Object?, Object?>{
'width': arguments['width'],
'height': arguments['height'],
};
}

viewId = arguments['id']! as int;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
MethodChannel(embeddedBlockChannelName(viewId)),
(MethodCall call) async => null,
);
return 0;
});
});

tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform_views, null);
});

/// The native block saying it has content on screen — the only state in which the block asks
/// for horizontal drags at all.
Future<void> showContent(WidgetTester tester) async {
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
embeddedBlockChannelName(viewId),
const StandardMethodCodec().encodeMethodCall(
const MethodCall(
EmbeddedBlockMethods.report,
<String, Object>{'appearance': 'content'},
),
),
(ByteData? _) {},
);
await tester.pumpAndSettle();
}

Future<PageController> showBlockInPageView(WidgetTester tester) async {
final PageController pages = PageController();
addTearDown(pages.dispose);

await tester.pumpWidget(MediaQuery(
data: const MediaQueryData(gestureSettings: settings),
child: Directionality(
textDirection: TextDirection.ltr,
child: PageView(
controller: pages,
children: const <Widget>[
Column(
children: <Widget>[
SizedBox(height: 200),
MindboxEmbeddedBlock(placeSystemName: 'stories', height: 104),
],
),
SizedBox.expand(),
],
),
),
));
await tester.pumpAndSettle();
await showContent(tester);

return pages;
}

/// A finger crossing the screen the way a finger does — in small steps, not in one jump. The
/// step matters: whoever reaches its own slop on an earlier step closes the arena, and a block
/// that waits for 18 never gets to answer a parent that is done at 8.
Future<void> dragBy(WidgetTester tester, Offset start, double distance) async {
final TestGesture gesture = await tester.startGesture(start);
for (double moved = 0; moved < distance.abs(); moved += 4) {
await gesture.moveBy(Offset(4 * distance.sign, 0));
await tester.pump();
}
await gesture.up();
await tester.pumpAndSettle();
}

testWidgets('A drag on the block is the block\'s, and the page stays where it is',
(WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
try {
final PageController pages = await showBlockInPageView(tester);

await dragBy(tester, tester.getCenter(find.byType(AndroidView)), -600);

expect(pages.page, 0);
} finally {
debugDefaultTargetPlatformOverride = null;
}
});

testWidgets('A drag beside the block still turns the page', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
try {
final PageController pages = await showBlockInPageView(tester);

final Offset besideTheBlock = tester.getCenter(find.byType(AndroidView)) - const Offset(0, 150);
await dragBy(tester, besideTheBlock, -600);

expect(pages.page, 1);
} finally {
debugDefaultTargetPlatformOverride = null;
}
});

testWidgets('A block still loading leaves the drag to the page', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
try {
final PageController pages = PageController();
addTearDown(pages.dispose);

await tester.pumpWidget(MediaQuery(
data: const MediaQueryData(gestureSettings: settings),
child: Directionality(
textDirection: TextDirection.ltr,
child: PageView(
controller: pages,
children: const <Widget>[
Column(
children: <Widget>[
SizedBox(height: 200),
MindboxEmbeddedBlock(placeSystemName: 'stories', height: 104),
],
),
SizedBox.expand(),
],
),
),
));
await tester.pumpAndSettle();

await dragBy(tester, tester.getCenter(find.byType(AndroidView)), -600);

expect(pages.page, 1);
} finally {
debugDefaultTargetPlatformOverride = null;
}
});
});
}
3 changes: 2 additions & 1 deletion mindbox_android/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api 'cloud.mindbox:mobile-sdk:2.15.2'
api 'cloud.mindbox:mobile-sdk:2.16.0-rc'
compileOnly 'cloud.mindbox:mindbox-common:2.16.0-rc'
}
4 changes: 2 additions & 2 deletions mindbox_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ homepage: https://mindbox.cloud/
repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox_android

environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
sdk: ">=2.17.0 <4.0.0"
flutter: ">=3.0.0"

flutter:
plugin:
Expand Down
4 changes: 2 additions & 2 deletions mindbox_ios/ios/mindbox_ios.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ The implementation of 'mindbox' plugin for the iOS platform
s.source = { :path => '.' }
s.source_files = 'mindbox_ios/Sources/mindbox_ios/**/*.swift', 'Classes/MindboxFlutterAppDelegate.{h,m}'
s.dependency 'Flutter'
s.dependency 'Mindbox', '2.15.1'
s.dependency 'MindboxNotifications', '2.15.1'
s.dependency 'Mindbox', '2.16.0-rc'
s.dependency 'MindboxNotifications', '2.16.0-rc'
s.platform = :ios, '12.0'

# Flutter.framework does not contain a i386 slice.
Expand Down
2 changes: 1 addition & 1 deletion mindbox_ios/ios/mindbox_ios/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let package = Package(
.library(name: "mindbox-ios", targets: ["mindbox_ios"])
],
dependencies: [
.package(url: "https://github.com/mindbox-cloud/ios-sdk", exact: "2.15.1"),
.package(url: "https://github.com/mindbox-cloud/ios-sdk", exact: "2.16.0-rc"),
],
targets: [
.target(
Expand Down
4 changes: 2 additions & 2 deletions mindbox_ios/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ homepage: https://mindbox.cloud/
repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox_ios

environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
sdk: ">=2.17.0 <4.0.0"
flutter: ">=3.0.0"

flutter:
plugin:
Expand Down
4 changes: 2 additions & 2 deletions mindbox_platform_interface/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ homepage: https://mindbox.cloud/
repository: https://github.com/mindbox-cloud/flutter-sdk/tree/master/mindbox_platform_interface

environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
sdk: ">=2.17.0 <4.0.0"
flutter: ">=3.0.0"

dependencies:
flutter:
Expand Down