tru_sdk_flutter 1.0.5 copy "tru_sdk_flutter: ^1.0.5" to clipboard
tru_sdk_flutter: ^1.0.5 copied to clipboard

tru.ID flutter plugin.

example/lib/main.dart

/*
 * MIT License
 * Copyright (C) 2020 4Auth Limited. All rights reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
import 'dart:convert';
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:tru_sdk_flutter/tru_sdk_flutter.dart';
import 'src/http/mock_client.dart';
import 'package:crypto/crypto.dart';
import '.env.dart';

// Set up a local tunnel base url.
final String baseURL = "https://080a-2a00-23c7-8589-8d01-ed63-2488-2ec5-ed08.ngrok.io";


void main() {
  runApp(PhoneCheckApp());
}

class PhoneCheckApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'tru.ID Phone Check Sample',
      theme: ThemeData(),
      home: PhoneCheckHome(title: 'tru.ID Flutter Sample App'),
    );
  }
}

class PhoneCheckHome extends StatefulWidget {
  PhoneCheckHome({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _PhoneCheckAppState createState() => _PhoneCheckAppState();
}

class _PhoneCheckAppState extends State<PhoneCheckHome> {
  Future<CheckStatus>? _futurePhoneCheck;
  String? _result = null;
  final _formKey = GlobalKey<FormState>();
  String? phoneNumber;
  bool agreedToTerms = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'tru.ID Sample App',
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.white,
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('tru.ID Flutter Sample App'),
        ),
        body: bodyContainer(),
      ),
    );
  }

  Container bodyContainer() {
    return Container(
      alignment: Alignment.center,
      padding: const EdgeInsets.all(8.0),
      child: (_futurePhoneCheck == null) ? bodyForm() : buildFutureBuilder(),
    );
  }

  Form bodyForm() {
    return Form(
      key: _formKey,
      child: Scrollbar(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              logo(),
              const SizedBox(height: 24),
              validatingTextField(),
              const SizedBox(height: 24),
              validatingFormField(),
              const SizedBox(height: 24),
              verifyButton(),
              Text((_result == null) ? "" : "Results $_result")
            ],
          ),
        ),
      ),
    );
  }

  FutureBuilder<CheckStatus> buildFutureBuilder() {
    return FutureBuilder<CheckStatus>(
      future: _futurePhoneCheck,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasData) {
            _result = 'Match status: ${snapshot.data!.match}';
          } else if (snapshot.hasError) {
            _result = 'Error:${snapshot.error}';
          }
          return bodyForm();
        } else if (snapshot.connectionState == ConnectionState.active ||
            snapshot.connectionState == ConnectionState.waiting ||
            snapshot.connectionState == ConnectionState.none) {
          print("-->");
        }
        return CircularProgressIndicator();
      },
    );
  }

  Widget logo() {
    return Center(
      child: Image.asset('assets/images/1024.png',
          width: 175.0, height: 100.0, fit: BoxFit.cover),
    );
  }

  // A text field that validates that the text is a phone number.
  TextFormField validatingTextField() {
    return TextFormField(
      //autofocus: true,
      initialValue: (phoneNumber == null) ? null : phoneNumber,
      keyboardType: TextInputType.phone,
      textInputAction: TextInputAction.next,
      validator: (value) {
        if (value!.isEmpty) {
          return 'Please enter a phone number.';
        }
        RegExp exp = RegExp(r"^(?:[+0][1-9])?[0-9]{10,12}$");
        if (exp.hasMatch(value)) {
          return null;
        }
        return 'Not a valid phone number';
      },
      decoration: const InputDecoration(
        filled: true,
        hintText: 'e.g. +447830305594',
        labelText: 'Enter phone number',
      ),
      onChanged: (value) {
        phoneNumber = value;
      },
    );
  }

  // A custom form field that requires the user to check a checkbox.
  FormField<bool> validatingFormField() {
    return FormField<bool>(
      initialValue: agreedToTerms,
      validator: (value) {
        if (value == false) {
          return 'You must agree to the terms of service.';
        }
        return null;
      },
      builder: (formFieldState) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Checkbox(
                  value: agreedToTerms,
                  onChanged: (value) {
                    // When the value of the checkbox changes,
                    // update the FormFieldState so the form is
                    // re-validated.
                    formFieldState.didChange(value);
                    setState(() {
                      agreedToTerms = value!;
                    });
                  },
                ),
                Text(
                  'I agree to the terms of service.',
                  style: Theme.of(context).textTheme.subtitle1,
                ),
              ],
            ),
            if (!formFieldState.isValid)
              Text(
                formFieldState.errorText ?? "",
                style: Theme.of(context)
                    .textTheme
                    .caption!
                    .copyWith(color: Theme.of(context).errorColor),
              ),
          ],
        );
      },
    );
  }

  Widget verifyButton() {
    return TextButton(
      child: const Text('Verify my phone number'),
      style: TextButton.styleFrom(
        primary: Colors.white,
        backgroundColor: Colors.blue,
      ),
      onPressed: () {
        // Validate the form by getting the FormState from the GlobalKey
        // and calling validate() on it.
        var valid = _formKey.currentState!.validate();
        if (!valid) {
          return;
        }
        if (phoneNumber != null) {
          FocusScope.of(context).unfocus();
          setState(() {
            _futurePhoneCheck = executeFlow(phoneNumber!);
          });
        }
      },
    );
  }

  // Get Coverage Access Token

 Future<TokenResponse>getCoverageAccessToken() async {
    var signature = sha256.convert(utf8.encode(RTA_KEY));
  final response = await http.get(
     Uri.parse('$RTA_URL/coverage_access_token'),
     headers: <String, String>{
       'x-rta': signature.toString(),
        },
  );
  if (response.statusCode == 200) {
    return TokenResponse.fromJson(jsonDecode(response.body));
  } else {
    throw Exception('Failed to get coverage access token: No access token');
  }
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<CheckStatus> executeFlow(String phoneNumber) async {
    print("[Reachability] - Start");
    var canMoveToNextStep = false;
    var tokenResponse = await getCoverageAccessToken();
    var token = tokenResponse.accessToken;
    TruSdkFlutter sdk = TruSdkFlutter();
    try {
      Map reach = await sdk.openWithDataCellularAndAccessToken(
          "https://eu.api.tru.id/coverage/v0.1/device_ip", token , true);
      print("isReachable = $reach");
      if (reach.containsKey("error")) {
        throw Exception(
            'Status = ${reach["error"]} - ${reach["error_description"]}');
      } else if (reach.containsKey("http_status")) {
          if (reach["http_status"] == 200 && reach["response_body"] != null) {
            Map<dynamic, dynamic> body = reach["response_body"];
            Coverage cv = Coverage.fromJson(body);
            print("body  ${cv.networkName}");
            if (cv.products != null) print("product  ${cv.products![0]}");
            //everything is fine
            canMoveToNextStep = true;
          } else if (reach["http_status"] == 400 && reach["response_body"] != null) {
            ApiError body = ApiError.fromJson(reach["response_body"]);
            print("[Is Reachable 400] ${body.detail}");
          } else if (reach["http_status"] == 412 && reach["response_body"] != null) {
            ApiError body = ApiError.fromJson(reach["response_body"]);
            print("[Is Reachable 412] ${body.detail}");
          } else if (reach["response_body"] != null) {
            ApiError body = ApiError.fromJson(reach["response_body"]);
            print("[Is Reachable ] ${body.detail}");
          }
      }
      if (canMoveToNextStep) {
        print("Moving on with Creating PhoneCheck...");
        final response = await http.post(
          Uri.parse('$baseURL/v0.2/phone-check'),
          headers: <String, String>{
            'content-type': 'application/json; charset=UTF-8',
          },
          body: jsonEncode(<String, String>{
            'phone_number': phoneNumber,
          }),
        );
        print("[PhoneCheck] - Received response");

        if (response.statusCode == 200) {
          PhoneCheck checkDetails =
              PhoneCheck.fromJson(jsonDecode(response.body));
          Map result = await sdk.openWithDataCellular(checkDetails.url, false);
          print("openWithDataCellular Results -> $result");
          if (result.containsKey("error")) {
            print(result);
            throw Exception('Error in openWithDataCellular: $result');
          } else if (result["http_status"] == 200 && result["response_body"] != null)
          {
              if (result["response_body"].containsKey("error")) {
                CheckErrorBody body = CheckErrorBody.fromJson(result["response_body"]);
                print("CheckErrorBody: ${body.description}");
                throw Exception('openWithDataCellular error ${body.description}');
              } else {
                CheckSuccessBody body = CheckSuccessBody.fromJson(result["response_body"]);
                 print('CheckSuccessBody $body');
                 try {
                   return exchangeCode(body.checkId, body.code, body.referenceId);
                 } catch (error) {
                   print("Error retrieving check result $error");
                   throw Exception("Error retrieving check result");
                 }
              }

            } else {
              ApiError body = ApiError.fromJson(result["response_body"]);
              print("ApiError ${body.detail}");
              throw Exception("Error: ${body.detail}");

            }
        } else {
          throw Exception('Failed to create phone check');
        }
      } else {
        print("isReachable parsing error");
        throw Exception('reachability failed');
      }
    } on PlatformException catch (e) {
      print("isReachable Error: ${e.toString()}");
      throw Exception('reachability failed');
    }
  }
}

Future<CheckStatus> exchangeCode(
    String checkID, String code, String? referenceID) async {
  var body = jsonEncode(<String, String>{
    'code': code,
    'check_id': checkID,
    'reference_id': (referenceID != null) ? referenceID : ""
  });

  final response = await http.post(
    Uri.parse('$baseURL/v0.2/phone-check/exchange-code'),
    body: body,
    headers: <String, String>{
      'content-type': 'application/json; charset=UTF-8',
    },
  );
  print("response request ${response.request}");
  if (response.statusCode == 200) {
    CheckStatus exchangeCheckRes =
        CheckStatus.fromJson(jsonDecode(response.body));
    print("Exchange Check Result $exchangeCheckRes");
    if (exchangeCheckRes.match) {
      print("✅ successful PhoneCheck match");
    } else {
      print("❌ failed PhoneCheck match");
    }
    return exchangeCheckRes;
  } else {
    throw Exception('Failed to exchange Code');
  }
}

class TokenResponse {
  final String accessToken;
  TokenResponse({required this.accessToken});
  factory TokenResponse.fromJson(Map<dynamic, dynamic>json) {
    return TokenResponse(
        accessToken: json['access_token']
    );
  }
}

class PhoneCheck {
  final String id;
  final String url;

  PhoneCheck({required this.id, required this.url});

  factory PhoneCheck.fromJson(Map<dynamic, dynamic> json) {
    return PhoneCheck(
      id: json['check_id'],
      url: json['check_url'],
    );
  }
}

class CheckStatus {
  final String id;
  bool match = false;

  CheckStatus({required this.id, required this.match});

  factory CheckStatus.fromJson(Map<dynamic, dynamic> json) {
    return CheckStatus(
      id: json['check_id'],
      match: json['match'] == null ? false : json['match'],
    );
  }
}


// Set up a mock HTTP client.
final http.Client httpClient = MockClient();

Future<String> getMockData() {
  return Future.delayed(Duration(seconds: 2), () {
    return "PhoneCheck Mock data";
    // throw Exception("Custom Error");
  });
}

Future<String> asyncMockData() async {
  await Future.delayed(Duration(seconds: 10));
  return Future.value("PhoneCheck Mock data");
}