mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-03 23:03:29 +01:00 
			
		
		
		
	Added GitHub PAT support
This commit is contained in:
		@@ -60,4 +60,7 @@ class FDroid implements AppSource {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> additionalDataDefaults = [];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<GeneratedFormItem> moreSourceSettingsFormItems = [];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import 'dart:convert';
 | 
			
		||||
import 'package:http/http.dart';
 | 
			
		||||
import 'package:obtainium/components/generated_form.dart';
 | 
			
		||||
import 'package:obtainium/custom_errors.dart';
 | 
			
		||||
import 'package:obtainium/providers/settings_provider.dart';
 | 
			
		||||
import 'package:obtainium/providers/source_provider.dart';
 | 
			
		||||
 | 
			
		||||
class GitHub implements AppSource {
 | 
			
		||||
@@ -18,6 +19,14 @@ class GitHub implements AppSource {
 | 
			
		||||
    return url.substring(0, match.end);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<String> getCredentialPrefixIfAny() async {
 | 
			
		||||
    SettingsProvider settingsProvider = SettingsProvider();
 | 
			
		||||
    await settingsProvider.initializeSettings();
 | 
			
		||||
    String? creds =
 | 
			
		||||
        settingsProvider.getSettingString(moreSourceSettingsFormItems[0].id);
 | 
			
		||||
    return creds != null && creds.isNotEmpty ? '$creds@' : '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<APKDetails> getLatestAPKDetails(
 | 
			
		||||
      String standardUrl, List<String> additionalData) async {
 | 
			
		||||
@@ -29,7 +38,7 @@ class GitHub implements AppSource {
 | 
			
		||||
        ? additionalData[2]
 | 
			
		||||
        : null;
 | 
			
		||||
    Response res = await get(Uri.parse(
 | 
			
		||||
        'https://api.$host/repos${standardUrl.substring('https://$host'.length)}/releases'));
 | 
			
		||||
        'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases'));
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
      var releases = jsonDecode(res.body) as List<dynamic>;
 | 
			
		||||
 | 
			
		||||
@@ -124,4 +133,26 @@ class GitHub implements AppSource {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> additionalDataDefaults = ['true', 'true', ''];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<GeneratedFormItem> moreSourceSettingsFormItems = [
 | 
			
		||||
    GeneratedFormItem(
 | 
			
		||||
        label: 'GitHub Credentials (Increases Rate Limit)',
 | 
			
		||||
        id: 'github-creds',
 | 
			
		||||
        required: false,
 | 
			
		||||
        additionalValidators: [
 | 
			
		||||
          (value) {
 | 
			
		||||
            if (value != null && value.trim().isNotEmpty) {
 | 
			
		||||
              if (value
 | 
			
		||||
                      .split(':')
 | 
			
		||||
                      .where((element) => element.trim().isNotEmpty)
 | 
			
		||||
                      .length !=
 | 
			
		||||
                  2) {
 | 
			
		||||
                return 'PAT must be in this format: username:token';
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
          }
 | 
			
		||||
        ])
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -68,4 +68,7 @@ class GitLab implements AppSource {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> additionalDataDefaults = [];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<GeneratedFormItem> moreSourceSettingsFormItems = [];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -62,4 +62,7 @@ class IzzyOnDroid implements AppSource {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> additionalDataDefaults = [];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<GeneratedFormItem> moreSourceSettingsFormItems = [];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,4 +48,7 @@ class Mullvad implements AppSource {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> additionalDataDefaults = [];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<GeneratedFormItem> moreSourceSettingsFormItems = [];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,4 +41,7 @@ class Signal implements AppSource {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> additionalDataDefaults = [];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<GeneratedFormItem> moreSourceSettingsFormItems = [];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -65,4 +65,7 @@ class SourceForge implements AppSource {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<String> additionalDataDefaults = [];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  List<GeneratedFormItem> moreSourceSettingsFormItems = [];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,13 +10,15 @@ class GeneratedFormItem {
 | 
			
		||||
  late bool required;
 | 
			
		||||
  late int max;
 | 
			
		||||
  late List<String? Function(String? value)> additionalValidators;
 | 
			
		||||
  late String id;
 | 
			
		||||
 | 
			
		||||
  GeneratedFormItem(
 | 
			
		||||
      {this.label = 'Input',
 | 
			
		||||
      this.type = FormItemType.string,
 | 
			
		||||
      this.required = true,
 | 
			
		||||
      this.max = 1,
 | 
			
		||||
      this.additionalValidators = const []});
 | 
			
		||||
      this.additionalValidators = const [],
 | 
			
		||||
      this.id = 'input'});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class GeneratedForm extends StatefulWidget {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ import 'package:dynamic_color/dynamic_color.dart';
 | 
			
		||||
import 'package:device_info_plus/device_info_plus.dart';
 | 
			
		||||
 | 
			
		||||
const String currentReleaseTag =
 | 
			
		||||
    'v0.4.1-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
 | 
			
		||||
    'v0.5.0-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
 | 
			
		||||
 | 
			
		||||
const String bgUpdateCheckTaskName = 'bg-update-check';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:http/http.dart';
 | 
			
		||||
import 'package:obtainium/app_sources/github.dart';
 | 
			
		||||
import 'package:obtainium/custom_errors.dart';
 | 
			
		||||
import 'package:obtainium/providers/source_provider.dart';
 | 
			
		||||
 | 
			
		||||
@@ -14,7 +15,7 @@ class GitHubStars implements MassAppSource {
 | 
			
		||||
  Future<List<String>> getOnePageOfUserStarredUrls(
 | 
			
		||||
      String username, int page) async {
 | 
			
		||||
    Response res = await get(Uri.parse(
 | 
			
		||||
        'https://api.github.com/users/$username/starred?per_page=100&page=$page'));
 | 
			
		||||
        'https://${await GitHub().getCredentialPrefixIfAny()}api.github.com/users/$username/starred?per_page=100&page=$page'));
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
      return (jsonDecode(res.body) as List<dynamic>)
 | 
			
		||||
          .map((e) => e['html_url'] as String)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:obtainium/components/custom_app_bar.dart';
 | 
			
		||||
import 'package:obtainium/components/generated_form.dart';
 | 
			
		||||
import 'package:obtainium/providers/settings_provider.dart';
 | 
			
		||||
import 'package:obtainium/providers/source_provider.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
import 'package:url_launcher/url_launcher_string.dart';
 | 
			
		||||
 | 
			
		||||
@@ -15,6 +17,7 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    SettingsProvider settingsProvider = context.watch<SettingsProvider>();
 | 
			
		||||
    SourceProvider sourceProvider = SourceProvider();
 | 
			
		||||
    if (settingsProvider.prefs == null) {
 | 
			
		||||
      settingsProvider.initializeSettings();
 | 
			
		||||
    }
 | 
			
		||||
@@ -22,8 +25,7 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
			
		||||
        backgroundColor: Theme.of(context).colorScheme.surface,
 | 
			
		||||
        body: CustomScrollView(slivers: <Widget>[
 | 
			
		||||
          const CustomAppBar(title: 'Settings'),
 | 
			
		||||
          SliverFillRemaining(
 | 
			
		||||
              hasScrollBody: true,
 | 
			
		||||
          SliverToBoxAdapter(
 | 
			
		||||
              child: Padding(
 | 
			
		||||
                  padding: const EdgeInsets.all(16),
 | 
			
		||||
                  child: settingsProvider.prefs == null
 | 
			
		||||
@@ -160,7 +162,7 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
			
		||||
                              height: 16,
 | 
			
		||||
                            ),
 | 
			
		||||
                            Text(
 | 
			
		||||
                              'More',
 | 
			
		||||
                              'Updates',
 | 
			
		||||
                              style: TextStyle(
 | 
			
		||||
                                  color: Theme.of(context).colorScheme.primary),
 | 
			
		||||
                            ),
 | 
			
		||||
@@ -204,33 +206,73 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
			
		||||
                                  .merge(const TextStyle(
 | 
			
		||||
                                      fontStyle: FontStyle.italic)),
 | 
			
		||||
                            ),
 | 
			
		||||
                            const Spacer(),
 | 
			
		||||
                            Row(
 | 
			
		||||
                              mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                              children: [
 | 
			
		||||
                                TextButton.icon(
 | 
			
		||||
                                  style: ButtonStyle(
 | 
			
		||||
                                    foregroundColor:
 | 
			
		||||
                                        MaterialStateProperty.resolveWith<
 | 
			
		||||
                                            Color>((Set<MaterialState> states) {
 | 
			
		||||
                                      return Colors.grey;
 | 
			
		||||
                                    }),
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  onPressed: () {
 | 
			
		||||
                                    launchUrlString(settingsProvider.sourceUrl,
 | 
			
		||||
                                        mode: LaunchMode.externalApplication);
 | 
			
		||||
                                  },
 | 
			
		||||
                                  icon: const Icon(Icons.code),
 | 
			
		||||
                                  label: Text(
 | 
			
		||||
                                    'Source',
 | 
			
		||||
                                    style:
 | 
			
		||||
                                        Theme.of(context).textTheme.bodySmall,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                )
 | 
			
		||||
                              ],
 | 
			
		||||
                            const Divider(
 | 
			
		||||
                              height: 48,
 | 
			
		||||
                            ),
 | 
			
		||||
                            Text(
 | 
			
		||||
                              'Source-Specific',
 | 
			
		||||
                              style: TextStyle(
 | 
			
		||||
                                  color: Theme.of(context).colorScheme.primary),
 | 
			
		||||
                            ),
 | 
			
		||||
                            ...sourceProvider.sources.map((e) {
 | 
			
		||||
                              if (e.moreSourceSettingsFormItems.isNotEmpty) {
 | 
			
		||||
                                return GeneratedForm(
 | 
			
		||||
                                    items: e.moreSourceSettingsFormItems
 | 
			
		||||
                                        .map((e) => [e])
 | 
			
		||||
                                        .toList(),
 | 
			
		||||
                                    onValueChanges: (values, valid) {
 | 
			
		||||
                                      if (valid) {
 | 
			
		||||
                                        for (var i = 0;
 | 
			
		||||
                                            i < values.length;
 | 
			
		||||
                                            i++) {
 | 
			
		||||
                                          settingsProvider.setSettingString(
 | 
			
		||||
                                              e.moreSourceSettingsFormItems[i]
 | 
			
		||||
                                                  .id,
 | 
			
		||||
                                              values[i]);
 | 
			
		||||
                                        }
 | 
			
		||||
                                      }
 | 
			
		||||
                                    },
 | 
			
		||||
                                    defaultValues:
 | 
			
		||||
                                        e.moreSourceSettingsFormItems.map((e) {
 | 
			
		||||
                                      return settingsProvider
 | 
			
		||||
                                              .getSettingString(e.id) ??
 | 
			
		||||
                                          '';
 | 
			
		||||
                                    }).toList());
 | 
			
		||||
                              } else {
 | 
			
		||||
                                return Container();
 | 
			
		||||
                              }
 | 
			
		||||
                            }),
 | 
			
		||||
                          ],
 | 
			
		||||
                        )))
 | 
			
		||||
                        ))),
 | 
			
		||||
          SliverToBoxAdapter(
 | 
			
		||||
            child: Column(
 | 
			
		||||
              children: [
 | 
			
		||||
                const SizedBox(
 | 
			
		||||
                  height: 16,
 | 
			
		||||
                ),
 | 
			
		||||
                TextButton.icon(
 | 
			
		||||
                  style: ButtonStyle(
 | 
			
		||||
                    foregroundColor: MaterialStateProperty.resolveWith<Color>(
 | 
			
		||||
                        (Set<MaterialState> states) {
 | 
			
		||||
                      return Colors.grey;
 | 
			
		||||
                    }),
 | 
			
		||||
                  ),
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    launchUrlString(settingsProvider.sourceUrl,
 | 
			
		||||
                        mode: LaunchMode.externalApplication);
 | 
			
		||||
                  },
 | 
			
		||||
                  icon: const Icon(Icons.code),
 | 
			
		||||
                  label: Text(
 | 
			
		||||
                    'Source',
 | 
			
		||||
                    style: Theme.of(context).textTheme.bodySmall,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                const SizedBox(
 | 
			
		||||
                  height: 16,
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          )
 | 
			
		||||
        ]));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -122,4 +122,12 @@ class SettingsProvider with ChangeNotifier {
 | 
			
		||||
    prefs?.setBool('showAppWebpage', show);
 | 
			
		||||
    notifyListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String? getSettingString(String settingId) {
 | 
			
		||||
    return prefs?.getString(settingId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setSettingString(String settingId, String value) {
 | 
			
		||||
    prefs?.setString(settingId, value);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -135,6 +135,7 @@ abstract class AppSource {
 | 
			
		||||
  AppNames getAppNames(String standardUrl);
 | 
			
		||||
  late List<List<GeneratedFormItem>> additionalDataFormItems;
 | 
			
		||||
  late List<String> additionalDataDefaults;
 | 
			
		||||
  late List<GeneratedFormItem> moreSourceSettingsFormItems;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
abstract class MassAppSource {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
			
		||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
			
		||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
			
		||||
# of the product and file versions while build-number is used as the build suffix.
 | 
			
		||||
version: 0.4.1+20 # When changing this, update the tag in main() accordingly
 | 
			
		||||
version: 0.5.0+21 # When changing this, update the tag in main() accordingly
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: '>=2.19.0-79.0.dev <3.0.0'
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user