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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.json.JSONObject
import org.json.JSONArray

Expand All @@ -24,6 +27,7 @@ class AtomicTransactFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAwa
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
private lateinit var activity : Activity
private var pausedTransactRef: PausedTransactRef? = null

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "atomic_transact_flutter")
Expand Down Expand Up @@ -138,6 +142,24 @@ class AtomicTransactFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAwa
Transact.close(activity)
} else if (call.method == "hideTransact") {
Transact.hideTransact(activity)
} else if (call.method == "pauseTransact") {
CoroutineScope(Dispatchers.Main).launch {
try {
pausedTransactRef = Transact.pauseTransact()
result.success(null)
} catch (e: Transact.PauseTransactException) {
result.error("PauseTransactError", e.message, null)
}
}
} else if (call.method == "resumeTransact") {
val ref = pausedTransactRef
if (ref != null) {
ref.resume(activity)
pausedTransactRef = null
result.success(null)
} else {
result.error("ResumeTransactError", "No paused Transact to resume", null)
}
} else {
result.notImplemented()
}
Expand Down
8 changes: 8 additions & 0 deletions example/lib/models/app_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ class AppState extends ChangeNotifier {
bool get debug => _debug;
set debug(bool v) { _debug = v; notifyListeners(); }

bool _pauseAfterInit = false;
bool get pauseAfterInit => _pauseAfterInit;
set pauseAfterInit(bool v) { _pauseAfterInit = v; notifyListeners(); }

int _pauseDelaySeconds = 3;
int get pauseDelaySeconds => _pauseDelaySeconds;
set pauseDelaySeconds(int v) { _pauseDelaySeconds = v; notifyListeners(); }

// Pay Link
PayLinkTask _payLinkTask = PayLinkTask.switchPayment;
PayLinkTask get payLinkTask => _payLinkTask;
Expand Down
72 changes: 65 additions & 7 deletions example/lib/screens/pay_link_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'company_login_screen.dart';
import '../widgets/public_token_banner.dart';
import '../widgets/select_grid.dart';

class PayLinkScreen extends StatelessWidget {
class PayLinkScreen extends StatefulWidget {
final AppState state;
final EventLog eventLog;
final VoidCallback onNavigateToSettings;
Expand All @@ -22,8 +22,30 @@ class PayLinkScreen extends StatelessWidget {
required this.onNavigateToSettings,
});

@override
State<PayLinkScreen> createState() => _PayLinkScreenState();
}

class _PayLinkScreenState extends State<PayLinkScreen> {
AppState get state => widget.state;
EventLog get eventLog => widget.eventLog;

bool _transactActive = false;
PausedTransactRef? _pausedRef;

void _onInitialize() {
final config = state.buildPayLinkConfig();
final shouldPause = state.pauseAfterInit;
final delaySeconds = state.pauseDelaySeconds;

setState(() {
_transactActive = true;
_pausedRef = null;
});

if (shouldPause) {
_schedulePause(delaySeconds);
}

Atomic.transact(
config: config,
Expand Down Expand Up @@ -75,6 +97,10 @@ class PayLinkScreen extends StatelessWidget {
));
},
onCompletion: (type, response, error) {
setState(() {
_transactActive = false;
_pausedRef = null;
});
if (type == AtomicTransactCompletionType.error) {
eventLog.add(EventEntry(
type: EventType.error,
Expand All @@ -99,6 +125,27 @@ class PayLinkScreen extends StatelessWidget {
);
}

void _schedulePause(int delaySeconds) {
Future.delayed(Duration(seconds: delaySeconds), () async {
if (!mounted || !_transactActive || _pausedRef != null) return;
try {
final ref = await Atomic.pauseTransact();
if (mounted) setState(() => _pausedRef = ref);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to pause: $e')),
);
}
}
});
}

Future<void> _onResume() async {
await _pausedRef?.resume();
setState(() => _pausedRef = null);
}

void _showConfigPreview(BuildContext context) {
final config = state.buildPayLinkConfig();
final json = const JsonEncoder.withIndent(' ').convert(config.toJson());
Expand Down Expand Up @@ -152,6 +199,21 @@ class PayLinkScreen extends StatelessWidget {
);
}

Widget _buildBottomButtons() {
if (_pausedRef != null) {
return FullWidthButton(
text: 'Resume',
onPressed: _onResume,
);
}
final label = state.pauseAfterInit ? 'Initialize and Pause' : 'Initialize';
return FullWidthButton(
text: label,
enabled: state.publicToken.isNotEmpty && !_transactActive,
onPressed: _onInitialize,
);
}

@override
Widget build(BuildContext context) {
return Column(
Expand All @@ -172,7 +234,7 @@ class PayLinkScreen extends StatelessWidget {
children: [
PublicTokenBanner(
publicToken: state.publicToken,
onNavigateToSettings: onNavigateToSettings,
onNavigateToSettings: widget.onNavigateToSettings,
),
SingleSelectGrid<PayLinkTask>(
title: 'Task',
Expand Down Expand Up @@ -214,11 +276,7 @@ class PayLinkScreen extends StatelessWidget {
),
),
),
FullWidthButton(
text: 'Initialize',
enabled: state.publicToken.isNotEmpty,
onPressed: _onInitialize,
),
_buildBottomButtons(),
const SizedBox(height: 16),
],
);
Expand Down
60 changes: 60 additions & 0 deletions example/lib/screens/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ class SettingsScreen extends StatelessWidget {
value: state.debug,
onChanged: (v) => state.debug = v,
),
const SizedBox(height: 16),
const Divider(indent: 16, endIndent: 16),
const SizedBox(height: 8),
const SectionHeader('Pause'),
ToggleRow(
title: 'Pause After Initialize',
subtitle: 'Automatically pause Transact after a delay',
value: state.pauseAfterInit,
onChanged: (v) => state.pauseAfterInit = v,
),
if (state.pauseAfterInit)
_PauseDelayPicker(
seconds: state.pauseDelaySeconds,
onChanged: (v) => state.pauseDelaySeconds = v,
),
const SizedBox(height: 32),
],
),
Expand Down Expand Up @@ -107,3 +122,48 @@ class _UrlModeSelector extends StatelessWidget {
);
}
}

class _PauseDelayPicker extends StatelessWidget {
final int seconds;
final ValueChanged<int> onChanged;

const _PauseDelayPicker({required this.seconds, required this.onChanged});

static const _options = [1, 2, 3, 5, 10, 15, 30];

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Delay (seconds)',
style: TextStyle(fontSize: 14, color: atomicOnSurfaceVariant),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: _options.map((s) {
final selected = s == seconds;
return ChoiceChip(
label: Text('${s}s'),
selected: selected,
onSelected: (_) => onChanged(s),
selectedColor: atomicPurple,
backgroundColor: atomicSurface,
side: BorderSide(
color: selected ? atomicPurple : atomicOutline,
),
labelStyle: TextStyle(
color: selected ? Colors.white : atomicOnBackground,
),
);
}).toList(),
),
],
),
);
}
}
72 changes: 65 additions & 7 deletions example/lib/screens/user_link_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import '../widgets/public_token_banner.dart';
import '../widgets/select_grid.dart';
import 'company_login_screen.dart';

class UserLinkScreen extends StatelessWidget {
class UserLinkScreen extends StatefulWidget {
final AppState state;
final EventLog eventLog;
final VoidCallback onNavigateToSettings;
Expand All @@ -22,8 +22,30 @@ class UserLinkScreen extends StatelessWidget {
required this.onNavigateToSettings,
});

@override
State<UserLinkScreen> createState() => _UserLinkScreenState();
}

class _UserLinkScreenState extends State<UserLinkScreen> {
AppState get state => widget.state;
EventLog get eventLog => widget.eventLog;

bool _transactActive = false;
PausedTransactRef? _pausedRef;

void _onInitialize() {
final config = state.buildUserLinkConfig();
final shouldPause = state.pauseAfterInit;
final delaySeconds = state.pauseDelaySeconds;

setState(() {
_transactActive = true;
_pausedRef = null;
});

if (shouldPause) {
_schedulePause(delaySeconds);
}

Atomic.transact(
config: config,
Expand Down Expand Up @@ -66,6 +88,10 @@ class UserLinkScreen extends StatelessWidget {
));
},
onCompletion: (type, response, error) {
setState(() {
_transactActive = false;
_pausedRef = null;
});
if (type == AtomicTransactCompletionType.error) {
eventLog.add(EventEntry(
type: EventType.error,
Expand All @@ -89,6 +115,27 @@ class UserLinkScreen extends StatelessWidget {
);
}

void _schedulePause(int delaySeconds) {
Future.delayed(Duration(seconds: delaySeconds), () async {
if (!mounted || !_transactActive || _pausedRef != null) return;
try {
final ref = await Atomic.pauseTransact();
if (mounted) setState(() => _pausedRef = ref);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to pause: $e')),
);
}
}
});
}

Future<void> _onResume() async {
await _pausedRef?.resume();
setState(() => _pausedRef = null);
}

void _showConfigPreview(BuildContext context) {
final config = state.buildUserLinkConfig();
final json = const JsonEncoder.withIndent(' ').convert(config.toJson());
Expand Down Expand Up @@ -142,6 +189,21 @@ class UserLinkScreen extends StatelessWidget {
);
}

Widget _buildBottomButtons() {
if (_pausedRef != null) {
return FullWidthButton(
text: 'Resume',
onPressed: _onResume,
);
}
final label = state.pauseAfterInit ? 'Initialize and Pause' : 'Initialize';
return FullWidthButton(
text: label,
enabled: state.publicToken.isNotEmpty && !_transactActive,
onPressed: _onInitialize,
);
}

@override
Widget build(BuildContext context) {
return Column(
Expand All @@ -162,7 +224,7 @@ class UserLinkScreen extends StatelessWidget {
children: [
PublicTokenBanner(
publicToken: state.publicToken,
onNavigateToSettings: onNavigateToSettings,
onNavigateToSettings: widget.onNavigateToSettings,
),
SingleSelectGrid<UserLinkTask>(
title: 'Task',
Expand Down Expand Up @@ -204,11 +266,7 @@ class UserLinkScreen extends StatelessWidget {
),
),
),
FullWidthButton(
text: 'Initialize',
enabled: state.publicToken.isNotEmpty,
onPressed: _onInitialize,
),
_buildBottomButtons(),
const SizedBox(height: 16),
],
);
Expand Down
Loading