import 'dart:convert'; import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../common/network/dio_provider.dart'; /// 金值 `jinzhi.v1.UpgradeReply` 的 HTTP protobuf 契约。 class UpgradeInfo { const UpgradeInfo({ required this.show, required this.force, required this.message, required this.url, required this.isLatestVersion, required this.latestVersion, required this.versionCode, required this.hash, }); final int show; final int force; final String message; final String url; final int isLatestVersion; final String latestVersion; final String versionCode; final String hash; bool get hasPublishedUpdate => isLatestVersion != 1 && url.trim().isNotEmpty && (latestVersion.trim().isNotEmpty || versionCode.trim().isNotEmpty); factory UpgradeInfo.fromProtobuf(List bytes) { final fields = _ProtoReader(bytes).readFields(); return UpgradeInfo( show: fields.integer(1), force: fields.integer(2), message: fields.string(3), url: fields.string(4), isLatestVersion: fields.integer(5), latestVersion: fields.string(6), versionCode: fields.string(7), hash: fields.string(8), ); } } // BLOCKED(proto): this endpoint has no authoritative generated Upgrade proto. // Keep its existing wire contract until that source becomes available. class UpdateApiClient { UpdateApiClient(this._dio); final Dio _dio; Future checkUpgrade() async { final response = await _dio.post>( '/sys/upgrade', // Uint8List ensures Dio sends the empty protobuf as 0 bytes instead of // JSON-encoding an empty Dart List as `[]` (Content-Length: 2). data: Uint8List(0), // jinzhi.v1.GetUpgradeRequest options: protobufOptions(), ); return UpgradeInfo.fromProtobuf(response.data ?? const []); } Future reportClick({ required String buildId, required String source, }) async { await _dio.post>( '/active/upgrade/click', data: _ProtoWriter() .string(1, buildId) .string(2, source) .toBytes(), // jinzhi.v1.UpgradeClickRequest options: protobufOptions(), ); } } final updateApiClientProvider = Provider( (ref) => UpdateApiClient(ref.watch(dioProvider)), ); class _ProtoFields { final _values = {}; int integer(int tag) => _values[tag] as int? ?? 0; String string(int tag) => _values[tag] as String? ?? ''; } class _ProtoReader { _ProtoReader(this._bytes); final List _bytes; var _index = 0; _ProtoFields readFields() { final result = _ProtoFields(); while (_index < _bytes.length) { final key = _varint(); final tag = key >> 3; switch (key & 7) { case 0: result._values[tag] = _varint(); case 2: final length = _varint(); final end = _index + length; if (end > _bytes.length) { throw const FormatException('Invalid protobuf'); } result._values[tag] = utf8.decode(_bytes.sublist(_index, end)); _index = end; default: throw const FormatException('Unsupported protobuf wire type'); } } return result; } int _varint() { var value = 0; var shift = 0; while (_index < _bytes.length && shift < 64) { final byte = _bytes[_index++]; value |= (byte & 0x7f) << shift; if (byte < 0x80) return value; shift += 7; } throw const FormatException('Invalid protobuf varint'); } } class _ProtoWriter { final _bytes = BytesBuilder(copy: false); _ProtoWriter string(int tag, String value) { final bytes = utf8.encode(value); _varint((tag << 3) | 2); _varint(bytes.length); _bytes.add(bytes); return this; } Uint8List toBytes() => _bytes.toBytes(); void _varint(int value) { while (value > 0x7f) { _bytes.addByte((value & 0x7f) | 0x80); value >>= 7; } _bytes.addByte(value); } }