From 5c4ca27c15f99a6fc37ae017018897ba48bc88d7 Mon Sep 17 00:00:00 2001 From: "Sourav Ganguly (isouravganguly)" Date: Mon, 16 Mar 2026 14:58:42 +0530 Subject: [PATCH 1/4] release(prod): loan version 5.1.1 --- loans/CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/loans/CHANGELOG.md b/loans/CHANGELOG.md index c245d83..f6b544c 100644 --- a/loans/CHANGELOG.md +++ b/loans/CHANGELOG.md @@ -1,7 +1,11 @@ +## 5.1.1 + +- Update Android and iOS native SDK dependencies for the 5.1.1 release. + ## 5.0.1 -* Update Android and iOS native SDK dependencies for the 5.0.1 release. +- Update Android and iOS native SDK dependencies for the 5.0.1 release. ## 0.0.1 -* Introducing loans from smallcase +- Introducing loans from smallcase From e5eccf0f9ea9ab7c82dfd3f464b6b96f4d5908cb Mon Sep 17 00:00:00 2001 From: "Sourav Ganguly (isouravganguly)" Date: Thu, 2 Jul 2026 15:35:05 +0530 Subject: [PATCH 2/4] feat(loans): support colorScheme param for LAS dark-theme launch --- .../example/scloans/ScLoanFlutterPlugin.kt | 35 ++++---- loans/ios/Classes/ScLoanFlutterPlugin.swift | 89 +++++++++++-------- loans/lib/sc_loan.dart | 30 +++---- loans/lib/sc_loan_props.dart | 7 +- smart_investing/lib/app/SILoansPage.dart | 76 +++++++++++----- 5 files changed, 146 insertions(+), 91 deletions(-) diff --git a/loans/android/src/main/kotlin/com/example/scloans/ScLoanFlutterPlugin.kt b/loans/android/src/main/kotlin/com/example/scloans/ScLoanFlutterPlugin.kt index 5725d50..697581b 100644 --- a/loans/android/src/main/kotlin/com/example/scloans/ScLoanFlutterPlugin.kt +++ b/loans/android/src/main/kotlin/com/example/scloans/ScLoanFlutterPlugin.kt @@ -58,6 +58,21 @@ class ScLoanFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } + // colorScheme rides the bridge as a primitive string and is mapped back to the native + // enum here. Unknown/absent → null so the SDK keeps its light-default behaviour. + private fun parseColorScheme(value: String?): ScLoanColorScheme? = when (value) { + "dark" -> ScLoanColorScheme.DARK + "light" -> ScLoanColorScheme.LIGHT + "system" -> ScLoanColorScheme.SYSTEM + else -> null + } + + private fun buildLoanInfo(call: MethodCall): ScLoanInfo { + val interactionToken: String = call.argument("interactionToken") ?: "" + val colorScheme = parseColorScheme(call.argument("colorScheme")) + return ScLoanInfo(interactionToken, colorScheme) + } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { val safeResult = ScLoanMethodChannelResult(result, activity) @@ -88,11 +103,9 @@ class ScLoanFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { ) } "apply" -> { - val interactionToken: String? = call.argument("interactionToken") - ScLoan.apply( activity, - ScLoanInfo(interactionToken ?: ""), + buildLoanInfo(call), listener = object : ScLoanResult { override fun onFailure(error: ScLoanError) { safeResult.error(error.code.toString(), error.message, error.toString()) @@ -106,11 +119,9 @@ class ScLoanFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { ) } "pay" -> { - val interactionToken: String? = call.argument("interactionToken") - ScLoan.pay( activity, - ScLoanInfo(interactionToken ?: ""), + buildLoanInfo(call), listener = object : ScLoanResult { override fun onFailure(error: ScLoanError) { safeResult.error(error.code.toString(), error.message, error.toString()) @@ -124,11 +135,9 @@ class ScLoanFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { ) } "withdraw" -> { - val interactionToken: String? = call.argument("interactionToken") - ScLoan.withdraw( activity, - ScLoanInfo(interactionToken ?: ""), + buildLoanInfo(call), listener = object : ScLoanResult { override fun onFailure(error: ScLoanError) { safeResult.error(error.code.toString(), error.message, error.toString()) @@ -142,11 +151,9 @@ class ScLoanFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { ) } "service" -> { - val interactionToken: String? = call.argument("interactionToken") - ScLoan.service( activity, - ScLoanInfo(interactionToken ?: ""), + buildLoanInfo(call), listener = object : ScLoanResult { override fun onFailure(error: ScLoanError) { safeResult.error(error.code.toString(), error.message, error.toString()) @@ -160,11 +167,9 @@ class ScLoanFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { ) } "triggerInteraction" -> { - val interactionToken: String? = call.argument("interactionToken") - ScLoan.triggerInteraction( activity, - ScLoanInfo(interactionToken ?: ""), + buildLoanInfo(call), listener = object : ScLoanResult { override fun onFailure(error: ScLoanError) { safeResult.error(error.code.toString(), error.message, error.toString()) diff --git a/loans/ios/Classes/ScLoanFlutterPlugin.swift b/loans/ios/Classes/ScLoanFlutterPlugin.swift index b388c93..0c167d7 100644 --- a/loans/ios/Classes/ScLoanFlutterPlugin.swift +++ b/loans/ios/Classes/ScLoanFlutterPlugin.swift @@ -57,28 +57,46 @@ public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin { registrar.addMethodCallDelegate(instance, channel: channel) } + // colorScheme rides the bridge as a primitive string and is mapped back to the native + // enum here. Unknown/absent → token-only initializer so the SDK keeps its light-default. + private func loanInfo(from args: Dictionary) -> ScLoanInfo? { + guard let interactionToken = args["interactionToken"] as? String else { return nil } + if let scheme = args["colorScheme"] as? String { + switch scheme { + case "dark": + return ScLoanInfo(interactionToken: interactionToken, colorScheme: .dark) + case "light": + return ScLoanInfo(interactionToken: interactionToken, colorScheme: .light) + case "system": + return ScLoanInfo(interactionToken: interactionToken, colorScheme: .system) + default: + break + } + } + return ScLoanInfo(interactionToken: interactionToken) + } + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - + guard let args = call.arguments as? Dictionary else { return } - + switch (call.method) { - + case "setup": do { guard let gateway = args["gateway"] as? String else { - //SDK setup failure print("ScLoan setup failed: No gateway name provided") return } - + var env = SCLoanEnvironment.production - + if let hostEnv = args["env"] as? Int { env = SCLoanEnvironment(rawValue: hostEnv) ?? .production } - + ScLoan.instance.setup( config: ScLoanConfig(gatewayName: gateway, environment: env)) { success, error in - + if let err = error { result(self.convertErrorToFlutterError(error: err)) } else { @@ -86,18 +104,17 @@ public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin { } } } - + case "apply": do { - guard let interactionToken = args["interactionToken"] as? String else { - //SDK setup failure + guard let info = loanInfo(from: args) else { print("ScLoan apply failed: No interactionToken provided") return } - + ScLoan.instance.apply( presentingController: currentViewController, - loanInfo: ScLoanInfo(interactionToken: interactionToken)) { success, error in - + loanInfo: info) { success, error in + if let err = error { result(self.convertErrorToFlutterError(error: err)) } else { @@ -105,18 +122,17 @@ public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin { } } } - + case "pay": do { - guard let interactionToken = args["interactionToken"] as? String else { - //SDK setup failure + guard let info = loanInfo(from: args) else { print("ScLoan pay failed: No interactionToken provided") return } - + ScLoan.instance.pay( presentingController: currentViewController, - loanInfo: ScLoanInfo(interactionToken: interactionToken)) { success, error in - + loanInfo: info) { success, error in + if let err = error { result(self.convertErrorToFlutterError(error: err)) } else { @@ -124,18 +140,17 @@ public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin { } } } - + case "withdraw": do { - guard let interactionToken = args["interactionToken"] as? String else { - //SDK setup failure + guard let info = loanInfo(from: args) else { print("ScLoan withdraw failed: No interactionToken provided") return } - + ScLoan.instance.withdraw( presentingController: currentViewController, - loanInfo: ScLoanInfo(interactionToken: interactionToken)) { success, error in - + loanInfo: info) { success, error in + if let err = error { result(self.convertErrorToFlutterError(error: err)) } else { @@ -143,18 +158,17 @@ public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin { } } } - + case "service": do { - guard let interactionToken = args["interactionToken"] as? String else { - //SDK setup failure + guard let info = loanInfo(from: args) else { print("ScLoan service failed: No interactionToken provided") return } - + ScLoan.instance.service( presentingController: currentViewController, - loanInfo: ScLoanInfo(interactionToken: interactionToken)) { success, error in - + loanInfo: info) { success, error in + if let err = error { result(self.convertErrorToFlutterError(error: err)) } else { @@ -163,16 +177,15 @@ public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin { } } case "triggerInteraction": do { - guard let interactionToken = args["interactionToken"] as? String else { - //SDK setup failure + guard let info = loanInfo(from: args) else { print("ScLoan triggerInteraction failed: No interactionToken provided") return } - + ScLoan.instance.triggerInteraction( presentingController: currentViewController, - loanInfo: ScLoanInfo(interactionToken: interactionToken)) { success, error in - + loanInfo: info) { success, error in + if let err = error { result(self.convertErrorToFlutterError(error: err)) } else { @@ -180,7 +193,7 @@ public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin { } } } - + default: result("Flutter method not implemented on iOS") } diff --git a/loans/lib/sc_loan.dart b/loans/lib/sc_loan.dart index 32c8fbf..851b38c 100644 --- a/loans/lib/sc_loan.dart +++ b/loans/lib/sc_loan.dart @@ -22,12 +22,18 @@ class ScLoan { } } + static Map _loanInfoArgs(ScLoanInfo loanInfo) { + return { + "interactionToken": loanInfo.interactionToken, + if (loanInfo.colorScheme != null) + "colorScheme": loanInfo.colorScheme!.name, + }; + } + static Future apply(ScLoanInfo loanInfo) async { try { String? applyResponse = - await _channel.invokeMethod('apply', { - "interactionToken": loanInfo.interactionToken, - }); + await _channel.invokeMethod('apply', _loanInfoArgs(loanInfo)); return ScLoanSuccess.fromJson(applyResponse ?? "{}"); } on PlatformException catch (e) { throw e.toScLoanError; @@ -37,9 +43,7 @@ class ScLoan { static Future pay(ScLoanInfo loanInfo) async { try { String? payResponse = - await _channel.invokeMethod('pay', { - "interactionToken": loanInfo.interactionToken, - }); + await _channel.invokeMethod('pay', _loanInfoArgs(loanInfo)); return ScLoanSuccess.fromJson(payResponse ?? "{}"); } on PlatformException catch (e) { throw e.toScLoanError; @@ -49,9 +53,7 @@ class ScLoan { static Future withdraw(ScLoanInfo loanInfo) async { try { String? withdrawResponse = - await _channel.invokeMethod('withdraw', { - "interactionToken": loanInfo.interactionToken, - }); + await _channel.invokeMethod('withdraw', _loanInfoArgs(loanInfo)); return ScLoanSuccess.fromJson(withdrawResponse ?? "{}"); } on PlatformException catch (e) { throw e.toScLoanError; @@ -61,9 +63,7 @@ class ScLoan { static Future service(ScLoanInfo loanInfo) async { try { String? serviceResponse = - await _channel.invokeMethod('service', { - "interactionToken": loanInfo.interactionToken, - }); + await _channel.invokeMethod('service', _loanInfoArgs(loanInfo)); return ScLoanSuccess.fromJson(serviceResponse ?? "{}"); } on PlatformException catch (e) { throw e.toScLoanError; @@ -72,10 +72,8 @@ class ScLoan { static Future triggerInteraction(ScLoanInfo loanInfo) async { try { - String? triggerInteractionResponse = - await _channel.invokeMethod('triggerInteraction', { - "interactionToken": loanInfo.interactionToken, - }); + String? triggerInteractionResponse = await _channel.invokeMethod( + 'triggerInteraction', _loanInfoArgs(loanInfo)); return ScLoanSuccess.fromJson(triggerInteractionResponse ?? "{}"); } on PlatformException catch (e) { throw e.toScLoanError; diff --git a/loans/lib/sc_loan_props.dart b/loans/lib/sc_loan_props.dart index 0d7c7a8..8b51e60 100644 --- a/loans/lib/sc_loan_props.dart +++ b/loans/lib/sc_loan_props.dart @@ -1,6 +1,8 @@ // ignore: constant_identifier_names enum ScLoanEnvironment { DEVELOPMENT, PRODUCTION, STAGING } +enum ScLoanColorScheme { dark, light, system } + class ScLoanConfig { final ScLoanEnvironment environment; final String gateway; @@ -10,6 +12,7 @@ class ScLoanConfig { class ScLoanInfo { final String interactionToken; + final ScLoanColorScheme? colorScheme; - const ScLoanInfo(this.interactionToken); -} \ No newline at end of file + const ScLoanInfo(this.interactionToken, {this.colorScheme}); +} diff --git a/smart_investing/lib/app/SILoansPage.dart b/smart_investing/lib/app/SILoansPage.dart index 2c9bee2..40f323d 100644 --- a/smart_investing/lib/app/SILoansPage.dart +++ b/smart_investing/lib/app/SILoansPage.dart @@ -10,9 +10,30 @@ import 'widgets/SISwitch.dart'; import 'widgets/SIText.dart'; import 'widgets/SITextField.dart'; -class SILoansPage extends StatelessWidget { +class SILoansPage extends StatefulWidget { const SILoansPage({super.key}); + @override + State createState() => _SILoansPageState(); +} + +class _SILoansPageState extends State { + static const _colorSchemeLabels = ['None', 'Light', 'Dark', 'System']; + static const _colorSchemeValues = [ + null, + ScLoanColorScheme.light, + ScLoanColorScheme.dark, + ScLoanColorScheme.system, + ]; + + int _colorSchemeIndex = 0; + + ScLoanInfo _buildLoanInfo() { + final token = repository.scLoanConfig.value.customInteractionToken ?? ""; + final scheme = _colorSchemeValues[_colorSchemeIndex]; + return ScLoanInfo(token, colorScheme: scheme); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -58,6 +79,31 @@ class SILoansPage extends StatelessWidget { ); }, ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SIText.large(text: "Color Scheme"), + const SizedBox(height: 6), + ToggleButtons( + isSelected: List.generate( + _colorSchemeLabels.length, + (i) => i == _colorSchemeIndex, + ), + onPressed: (i) => setState(() => _colorSchemeIndex = i), + borderRadius: BorderRadius.circular(8), + children: _colorSchemeLabels + .map((label) => Padding( + padding: + const EdgeInsets.symmetric(horizontal: 14), + child: Text(label), + )) + .toList(), + ), + ], + ), + ), Wrap( spacing: 12, children: [ @@ -78,9 +124,7 @@ class SILoansPage extends StatelessWidget { label: "Apply", onPressed: () async { try { - final response = await ScLoan.apply(ScLoanInfo(repository - .scLoanConfig.value.customInteractionToken ?? - "")); + final response = await ScLoan.apply(_buildLoanInfo()); repository.showAlertDialog(response.toString(), context); } on ScLoanError catch (e) { repository.showAlertDialog(e.toString(), context); @@ -91,9 +135,7 @@ class SILoansPage extends StatelessWidget { label: "Pay", onPressed: () async { try { - final response = await ScLoan.pay(ScLoanInfo(repository - .scLoanConfig.value.customInteractionToken ?? - "")); + final response = await ScLoan.pay(_buildLoanInfo()); repository.showAlertDialog( response.toString(), context); } on ScLoanError catch (e) { @@ -104,10 +146,8 @@ class SILoansPage extends StatelessWidget { label: "Withdraw", onPressed: () async { try { - final response = await ScLoan.withdraw(ScLoanInfo( - repository.scLoanConfig.value - .customInteractionToken ?? - "")); + final response = + await ScLoan.withdraw(_buildLoanInfo()); repository.showAlertDialog( response.toString(), context); } on ScLoanError catch (e) { @@ -118,24 +158,20 @@ class SILoansPage extends StatelessWidget { label: "Service", onPressed: () async { try { - final response = await ScLoan.service(ScLoanInfo( - repository.scLoanConfig.value - .customInteractionToken ?? - "")); + final response = + await ScLoan.service(_buildLoanInfo()); repository.showAlertDialog( response.toString(), context); } on ScLoanError catch (e) { repository.showAlertDialog(e.toString(), context); } }), - SIButton( + SIButton( label: "Trigger Interaction", onPressed: () async { try { - final response = await ScLoan.triggerInteraction(ScLoanInfo( - repository.scLoanConfig.value - .customInteractionToken ?? - "")); + final response = + await ScLoan.triggerInteraction(_buildLoanInfo()); repository.showAlertDialog( response.toString(), context); } on ScLoanError catch (e) { From e630ff25fc5b78771ac72c8c7a858f1a3ab0f0a8 Mon Sep 17 00:00:00 2001 From: "Sourav Ganguly (isouravganguly)" Date: Mon, 6 Jul 2026 20:17:30 +0530 Subject: [PATCH 3/4] bump loans SDK pin to sdk-sourav-native-dark-theme-cb54bf9 (5.1.4-92-release) --- loans/android/build.gradle | 2 +- loans/ios/scloans.podspec | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/loans/android/build.gradle b/loans/android/build.gradle index 5243d37..d6f2cad 100644 --- a/loans/android/build.gradle +++ b/loans/android/build.gradle @@ -64,5 +64,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.smallcase.loans:sdk:5.1.3' + implementation 'com.smallcase.loans:sdk-sourav-native-dark-theme-cb54bf9:5.1.4-92-release' } diff --git a/loans/ios/scloans.podspec b/loans/ios/scloans.podspec index 955c072..cd118af 100644 --- a/loans/ios/scloans.podspec +++ b/loans/ios/scloans.podspec @@ -19,5 +19,7 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' - s.dependency 'SCLoans', '7.2.0' + # INTERNAL TEST PIN: dark-theme branch build (mirrors the android/build.gradle pin). + # Revert to: s.dependency 'SCLoans', '7.2.0' + s.dependency 'SCLoans-sourav-native-dark-theme-37c97d9', '7.1.2-45-release' end From b64a2b7c25145d2e09a6662a415979d4570c3719 Mon Sep 17 00:00:00 2001 From: "Sourav Ganguly (isouravganguly)" Date: Wed, 8 Jul 2026 15:25:20 +0530 Subject: [PATCH 4/4] chore(loans): flag colorScheme .name mapping as fragile TODO --- loans/lib/sc_loan.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/loans/lib/sc_loan.dart b/loans/lib/sc_loan.dart index 851b38c..bf8ceff 100644 --- a/loans/lib/sc_loan.dart +++ b/loans/lib/sc_loan.dart @@ -25,6 +25,7 @@ class ScLoan { static Map _loanInfoArgs(ScLoanInfo loanInfo) { return { "interactionToken": loanInfo.interactionToken, + // TODO: use explicit value mapping, not enum .name (fragile to renames). if (loanInfo.colorScheme != null) "colorScheme": loanInfo.colorScheme!.name, };