[app] New AddRepoActivity in compose

This commit is contained in:
Torsten Grote
2023-07-17 17:00:19 -03:00
parent f3e8a0a45b
commit d21adc75b4
15 changed files with 1082 additions and 535 deletions

View File

@@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
// add -Pstrict.release to the gradle command line to enable
if (project.hasProperty('strict.release')) {
@@ -100,6 +101,14 @@ android {
cruncherEnabled = false
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.6"
}
testOptions {
unitTests {
includeAndroidResources = true
@@ -164,6 +173,7 @@ dependencies {
implementation 'com.google.android.material:material:1.9.0'
implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false }
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24 or library desugering
implementation 'info.guardianproject.netcipher:netcipher:2.2.0-alpha'
//noinspection GradleDependency -> Commons IO > 2.5 uses java.nio.file, which requires desugaring
@@ -178,6 +188,7 @@ dependencies {
implementation 'io.reactivex.rxjava3:rxjava:3.0.9'
implementation "com.github.bumptech.glide:glide:4.14.2"
implementation "com.github.bumptech.glide:compose:1.0.0-alpha.1"
annotationProcessor "com.github.bumptech.glide:compiler:4.14.2"
implementation 'org.bouncycastle:bcprov-jdk15to18:1.71'
@@ -186,6 +197,15 @@ dependencies {
fullImplementation 'org.jmdns:jmdns:3.5.5'
fullImplementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation platform('androidx.compose:compose-bom:2023.06.01')
implementation 'androidx.compose.material:material'
implementation 'androidx.compose.material:material-icons-extended'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose"
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.activity:activity-compose:1.7.2'
implementation "com.google.accompanist:accompanist-themeadapter-material:0.30.1"
debugImplementation 'androidx.compose.ui:ui-tooling'
testImplementation 'androidx.test:core:1.4.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.8.1'
@@ -196,7 +216,7 @@ dependencies {
androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:monitor:1.5.0'
androidTestImplementation 'androidx.test:monitor:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'

View File

@@ -67,6 +67,7 @@
android:allowBackup="true"
android:description="@string/app_description"
android:fullBackupContent="@xml/backup_rules"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:label="${applicationLabel}"
android:networkSecurityConfig="@xml/network_security_config"
@@ -91,6 +92,16 @@
android:launchMode="singleTask"
android:parentActivityName=".views.main.MainActivity"></activity>
<activity
android:name=".views.repos.AddRepoActivity"
android:launchMode="singleTask"
android:parentActivityName=".views.main.MainActivity" />
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
tools:replace="screenOrientation" />
<activity
android:name=".NfcNotEnabledActivity"
android:configChanges="layoutDirection|locale"

View File

@@ -1,441 +0,0 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.integration.android;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
* project's source code.</p>
*
* <h2>Initiating a barcode scan</h2>
*
* <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
* for the result in your app.</p>
*
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
* {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
*
* <p>There are a few steps to using this integration. First, your {@link AppCompatActivity} must implement
* the method {@link AppCompatActivity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
*
* <pre>{@code
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
* if (scanResult != null) {
* // handle scan result
* }
* // else continue with any other code you need in the method
* ...
* }
* }</pre>
*
* <p>This is where you will handle a scan result.</p>
*
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
*
* <pre>{@code
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
* integrator.initiateScan();
* }</pre>
*
* <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
* In particular, ideally, the app dismisses the dialog if it's still active in its
* {@link AppCompatActivity#onPause()}
* method.</p>
*
* <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
* {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
* yes/no button labels can be changed.</p>
*
* <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
* to invoke the scanner. This can be used to set additional options not directly exposed by this
* simplified API.</p>
*
* <p>By default, this will only allow applications that are known to respond to this intent correctly
* do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
* For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
*
* <h2>Sharing text via barcode</h2>
*
* <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
*
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
*
* <h2>Enabling experimental barcode formats</h2>
*
* <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
* PDF417. Use {@link #initiateScan(java.util.Collection)} with
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
* formats.</p>
*
* @author Sean Owen
* @author Fred Lin
* @author Isaac Potoczny-Jones
* @author Brad Drehmer
* @author gcstang
*/
@SuppressWarnings("LineLength")
public class IntentIntegrator {
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
public static final String DEFAULT_MESSAGE =
"This application requires a Barcode Scanner. Would you like to install one?";
public static final String DEFAULT_YES = "Yes";
public static final String DEFAULT_NO = "No";
private static final String BS_PACKAGE = "com.google.zxing.client.android";
// supported barcode formats
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
public static final Collection<String> ONE_D_CODE_TYPES =
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
"ITF", "RSS_14", "RSS_EXPANDED");
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
public static final Collection<String> ALL_CODE_TYPES = null;
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
private final AppCompatActivity activity;
private final Fragment fragment;
private String title;
private String message;
private String buttonYes;
private String buttonNo;
private final Map<String, Object> moreExtras = new HashMap<>(3);
/**
* @param activity {@link AppCompatActivity} invoking the integration
*/
public IntentIntegrator(AppCompatActivity activity) {
this.activity = activity;
this.fragment = null;
initializeConfiguration();
}
/**
* @param fragment {@link Fragment} invoking the integration.
* {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
* of an {@link AppCompatActivity}
*/
public IntentIntegrator(Fragment fragment) {
this.activity = (AppCompatActivity) fragment.getActivity();
this.fragment = fragment;
initializeConfiguration();
}
private void initializeConfiguration() {
title = DEFAULT_TITLE;
message = DEFAULT_MESSAGE;
buttonYes = DEFAULT_YES;
buttonNo = DEFAULT_NO;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setTitleByID(int titleID) {
title = activity.getString(titleID);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void setMessageByID(int messageID) {
message = activity.getString(messageID);
}
public String getButtonYes() {
return buttonYes;
}
public void setButtonYes(String buttonYes) {
this.buttonYes = buttonYes;
}
public void setButtonYesByID(int buttonYesID) {
buttonYes = activity.getString(buttonYesID);
}
public String getButtonNo() {
return buttonNo;
}
public void setButtonNo(String buttonNo) {
this.buttonNo = buttonNo;
}
public void setButtonNoByID(int buttonNoID) {
buttonNo = activity.getString(buttonNoID);
}
public Map<String, ?> getMoreExtras() {
return moreExtras;
}
public final void addExtra(String key, Object value) {
moreExtras.put(key, value);
}
/**
* Initiates a scan for all known barcode types with the default camera.
*
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan() {
return initiateScan(ALL_CODE_TYPES, -1);
}
/**
* Initiates a scan for all known barcode types with the specified camera.
*
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan(int cameraId) {
return initiateScan(ALL_CODE_TYPES, cameraId);
}
/**
* Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
* like {@link #PRODUCT_CODE_TYPES} for example.
*
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
return initiateScan(desiredBarcodeFormats, -1);
}
/**
* Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
* like {@link #PRODUCT_CODE_TYPES} for example.
*
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) {
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
// check which types of codes to scan for
if (desiredBarcodeFormats != null) {
// set the desired barcode types
StringBuilder joinedByComma = new StringBuilder();
for (String format : desiredBarcodeFormats) {
if (joinedByComma.length() > 0) {
joinedByComma.append(',');
}
joinedByComma.append(format);
}
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
}
// check requested camera ID
if (cameraId >= 0) {
intentScan.putExtra("SCAN_CAMERA_ID", cameraId);
}
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intentScan);
try {
startActivityForResult(intentScan, REQUEST_CODE);
} catch (ActivityNotFoundException ex) {
return showDownloadDialog();
}
return null;
}
/**
* Start an activity. This method is defined to allow different methods of activity starting for
* newer versions of Android and for compatibility library.
*
* @param intent Intent to start.
* @param code Request code for the activity
* @see android.app.AppCompatActivity#startActivityForResult(Intent, int)
* @see Fragment#startActivityForResult(Intent, int)
*/
protected void startActivityForResult(Intent intent, int code) {
if (fragment == null) {
activity.startActivityForResult(intent, code);
} else {
fragment.startActivityForResult(intent, code);
}
}
private AlertDialog showDownloadDialog() {
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
downloadDialog.setTitle(title);
downloadDialog.setMessage(message);
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String packageName = BS_PACKAGE;
Uri uri = Uri.parse("market://details?id=" + packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (fragment == null) {
activity.startActivity(intent);
} else {
fragment.startActivity(intent);
}
}
});
downloadDialog.setNegativeButton(buttonNo, null);
downloadDialog.setCancelable(true);
return downloadDialog.show();
}
/**
* <p>Call this from your {@link AppCompatActivity}'s
* {@link AppCompatActivity#onActivityResult(int, int, Intent)} method.</p>
*
* @param requestCode request code from {@code onActivityResult()}
* @param resultCode result code from {@code onActivityResult()}
* @param intent {@link Intent} from {@code onActivityResult()}
* @return null if the event handled here was not related to this class, or
* else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
* the fields will be null.
*/
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_CODE) {
if (resultCode == AppCompatActivity.RESULT_OK) {
String contents = intent.getStringExtra("SCAN_RESULT");
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
return new IntentResult(contents,
formatName,
rawBytes,
orientation,
errorCorrectionLevel);
}
return new IntentResult();
}
return null;
}
/**
* Defaults to type "TEXT_TYPE".
*
* @param text the text string to encode as a barcode
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
* @see #shareText(CharSequence, CharSequence)
*/
public final AlertDialog shareText(CharSequence text) {
return shareText(text, "TEXT_TYPE");
}
/**
* Shares the given text by encoding it as a barcode, such that another user can
* scan the text off the screen of the device.
*
* @param text the text string to encode as a barcode
* @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog shareText(CharSequence text, CharSequence type) {
Intent intent = new Intent();
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setAction(BS_PACKAGE + ".ENCODE");
intent.putExtra("ENCODE_TYPE", type);
intent.putExtra("ENCODE_DATA", text);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intent);
try {
if (fragment == null) {
activity.startActivity(intent);
} else {
fragment.startActivity(intent);
}
} catch (ActivityNotFoundException ex) {
return showDownloadDialog();
}
return null;
}
private static List<String> list(String... values) {
return Collections.unmodifiableList(Arrays.asList(values));
}
private void attachMoreExtras(Intent intent) {
for (Map.Entry<String, Object> entry : moreExtras.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// Kind of hacky
if (value instanceof Integer) {
intent.putExtra(key, (Integer) value);
} else if (value instanceof Long) {
intent.putExtra(key, (Long) value);
} else if (value instanceof Boolean) {
intent.putExtra(key, (Boolean) value);
} else if (value instanceof Double) {
intent.putExtra(key, (Double) value);
} else if (value instanceof Float) {
intent.putExtra(key, (Float) value);
} else if (value instanceof Bundle) {
intent.putExtra(key, (Bundle) value);
} else {
intent.putExtra(key, value.toString());
}
}
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.integration.android;
/**
* <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
*
* @author Sean Owen
*/
public final class IntentResult {
private final String contents;
private final String formatName;
private final byte[] rawBytes;
private final Integer orientation;
private final String errorCorrectionLevel;
IntentResult() {
this(null, null, null, null, null);
}
IntentResult(String contents,
String formatName,
byte[] rawBytes,
Integer orientation,
String errorCorrectionLevel) {
this.contents = contents;
this.formatName = formatName;
this.rawBytes = rawBytes;
this.orientation = orientation;
this.errorCorrectionLevel = errorCorrectionLevel;
}
/**
* @return raw content of barcode
*/
public String getContents() {
return contents;
}
/**
* @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
*/
public String getFormatName() {
return formatName;
}
/**
* @return raw bytes of the barcode content, if applicable, or null otherwise
*/
public byte[] getRawBytes() {
return rawBytes;
}
/**
* @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
*/
public Integer getOrientation() {
return orientation;
}
/**
* @return name of the error correction level used in the barcode, if applicable
*/
public String getErrorCorrectionLevel() {
return errorCorrectionLevel;
}
@Override
public String toString() {
int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
return "Format: " + formatName + '\n' + "Contents: " + contents + '\n' + "Raw bytes: (" + rawBytesLength
+ " bytes)\n" + "Orientation: " + orientation + '\n' + "EC level: " + errorCorrectionLevel + '\n';
}
}

View File

@@ -72,6 +72,7 @@ import org.fdroid.fdroid.nearby.PublicSourceDirProvider;
import org.fdroid.fdroid.nearby.SDCardScannerService;
import org.fdroid.fdroid.nearby.WifiStateChangeService;
import org.fdroid.fdroid.net.ConnectivityMonitorService;
import org.fdroid.fdroid.net.DownloaderFactory;
import org.fdroid.fdroid.panic.HidingManager;
import org.fdroid.fdroid.receiver.DeviceStorageReceiver;
import org.fdroid.fdroid.work.CleanCacheWorker;
@@ -508,7 +509,8 @@ public class FDroidApp extends Application implements androidx.work.Configuratio
public static Repository createSwapRepo(String address, String certificate) {
long now = System.currentTimeMillis();
return new Repository(42L, address, now, IndexFormatVersion.ONE, certificate, 20001L, 42, now);
return new Repository(42L, address, now, IndexFormatVersion.ONE, certificate, 20001L, 42,
now);
}
public static Context getInstance() {
@@ -516,7 +518,10 @@ public class FDroidApp extends Application implements androidx.work.Configuratio
}
public static RepoManager getRepoManager(Context context) {
if (repoManager == null) repoManager = new RepoManager(DBHelper.getDb(context));
if (repoManager == null) {
repoManager = new RepoManager(context, DBHelper.getDb(context), DownloaderFactory.INSTANCE,
DownloaderFactory.HTTP_MANAGER);
}
return repoManager;
}

View File

@@ -402,7 +402,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return Theme.valueOf(preferences.getString(Preferences.PREF_THEME, null));
}
boolean isPureBlack() {
public boolean isPureBlack() {
return preferences.getBoolean(Preferences.PREF_USE_PURE_BLACK_DARK_THEME, false);
}

View File

@@ -0,0 +1,102 @@
package org.fdroid.fdroid.compose
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import com.google.accompanist.themeadapter.material.createMdcTheme
import org.fdroid.fdroid.Preferences
import java.util.Locale
object ComposeUtils {
@Composable
fun FDroidContent(content: @Composable () -> Unit) {
val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
val (colors, typography, shapes) = createMdcTheme(
context = context,
layoutDirection = layoutDirection,
)
val newColors = (colors ?: MaterialTheme.colors).let { c ->
if (!c.isLight && Preferences.get().isPureBlack) c.copy(background = Color.Black)
else c
}
MaterialTheme(
colors = newColors,
typography = typography?.let {
// adapt letter-spacing to non-compose UI
it.copy(
body1 = it.body1.copy(letterSpacing = 0.em),
body2 = it.body2.copy(letterSpacing = 0.em),
)
} ?: MaterialTheme.typography,
shapes = shapes ?: MaterialTheme.shapes
) {
Surface(content = content)
}
}
@Composable
fun FDroidButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
imageVector: ImageVector? = null,
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(32.dp),
modifier = modifier.heightIn(min = ButtonDefaults.MinHeight)
) {
if (imageVector != null) {
Icon(
imageVector = imageVector,
contentDescription = text,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
}
Text(text = text.uppercase(Locale.getDefault()))
}
}
@Composable
fun FDroidOutlineButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
imageVector: ImageVector? = null,
) {
OutlinedButton(
onClick = onClick,
shape = RoundedCornerShape(32.dp),
modifier = modifier.heightIn(min = ButtonDefaults.MinHeight)
) {
if (imageVector != null) {
Icon(
imageVector = imageVector,
contentDescription = text,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
}
Text(text = text.uppercase(Locale.getDefault()))
}
}
}

View File

@@ -63,6 +63,7 @@ import org.fdroid.database.RepositoryDao;
import org.fdroid.download.Mirror;
import org.fdroid.fdroid.AddRepoIntentService;
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
@@ -71,6 +72,7 @@ import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.DBHelper;
import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.views.repos.AddRepoActivity;
import org.fdroid.index.RepoManager;
import org.fdroid.index.v1.IndexV1UpdaterKt;
@@ -134,7 +136,13 @@ public class ManageReposActivity extends AppCompatActivity implements RepoAdapte
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.action_add_repo) {
showAddRepo();
if (BuildConfig.DEBUG) {
// TODO enable this for all builds and remove dead code afterwards
Intent i = new Intent(this, AddRepoActivity.class);
startActivity(i);
} else {
showAddRepo();
}
return true;
}
return false;

View File

@@ -0,0 +1,63 @@
package org.fdroid.fdroid.views.repos
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.compose.runtime.collectAsState
import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import org.fdroid.fdroid.FDroidApp
import org.fdroid.fdroid.UpdateService
import org.fdroid.fdroid.compose.ComposeUtils.FDroidContent
import org.fdroid.fdroid.views.ManageReposActivity
import org.fdroid.repo.AddRepoError
import org.fdroid.repo.Added
class AddRepoActivity : ComponentActivity() {
private val repoManager = FDroidApp.getRepoManager(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(STARTED) {
repoManager.addRepoState.collect { state ->
if (state is Added) {
// update newly added repo
UpdateService.updateRepoNow(applicationContext, state.repo.address)
// show repo list and close this activity
val intent = Intent(applicationContext, ManageReposActivity::class.java)
startActivity(intent)
finish()
}
}
}
}
setContent {
FDroidContent {
val state = repoManager.addRepoState.collectAsState().value
BackHandler(state is AddRepoError) {
// reset state when going back on error screen
repoManager.abortAddingRepository()
}
AddRepoIntroScreen(
state = state,
onFetchRepo = { url ->
repoManager.fetchRepositoryPreview(url)
},
onAddRepo = { repoManager.addFetchedRepository() },
onBackClicked = { onBackPressedDispatcher.onBackPressed() },
)
}
}
}
override fun onDestroy() {
super.onDestroy()
if (!isChangingConfigurations) repoManager.abortAddingRepository()
}
}

View File

@@ -0,0 +1,108 @@
package org.fdroid.fdroid.views.repos
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.ContentAlpha
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Error
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.fdroid.fdroid.R
import org.fdroid.fdroid.compose.ComposeUtils
import org.fdroid.fdroid.views.ManageReposActivity.getDisallowInstallUnknownSourcesErrorMessage
import org.fdroid.repo.AddRepoError
import org.fdroid.repo.AddRepoError.ErrorType.INVALID_FINGERPRINT
import org.fdroid.repo.AddRepoError.ErrorType.INVALID_INDEX
import org.fdroid.repo.AddRepoError.ErrorType.IO_ERROR
import org.fdroid.repo.AddRepoError.ErrorType.UNKNOWN_SOURCES_DISALLOWED
import java.io.IOException
@Composable
fun AddRepoErrorScreen(paddingValues: PaddingValues, state: AddRepoError) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp, CenterVertically),
horizontalAlignment = CenterHorizontally,
modifier = Modifier
.padding(16.dp)
.padding(paddingValues)
.fillMaxSize(),
) {
Image(
imageVector = Icons.Default.Error,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colors.error),
modifier = Modifier.size(48.dp),
)
val title = when (state.errorType) {
INVALID_FINGERPRINT -> stringResource(R.string.bad_fingerprint)
UNKNOWN_SOURCES_DISALLOWED -> {
val context = LocalContext.current
getDisallowInstallUnknownSourcesErrorMessage(context)
}
INVALID_INDEX -> stringResource(R.string.repo_invalid)
IO_ERROR -> stringResource(R.string.repo_io_error)
}
Text(
text = title,
style = MaterialTheme.typography.h5,
textAlign = TextAlign.Center,
)
if (state.exception != null) Text(
text = state.exception.toString(),
style = MaterialTheme.typography.body1,
modifier = Modifier.alpha(ContentAlpha.medium),
)
}
}
@Preview
@Composable
fun AddRepoErrorInvalidFingerprintPreview() {
ComposeUtils.FDroidContent {
AddRepoErrorScreen(PaddingValues(0.dp), AddRepoError(INVALID_FINGERPRINT))
}
}
@Preview
@Composable
fun AddRepoErrorIoErrorPreview() {
ComposeUtils.FDroidContent {
AddRepoErrorScreen(PaddingValues(0.dp), AddRepoError(IO_ERROR, IOException("foo bar")))
}
}
@Preview
@Composable
fun AddRepoErrorInvalidIndexPreview() {
ComposeUtils.FDroidContent {
AddRepoErrorScreen(
PaddingValues(0.dp),
AddRepoError(INVALID_INDEX, RuntimeException("foo bar"))
)
}
}
@Preview
@Composable
fun AddRepoErrorUnknownSourcesPreview() {
ComposeUtils.FDroidContent {
AddRepoErrorScreen(PaddingValues(0.dp), AddRepoError(UNKNOWN_SOURCES_DISALLOWED))
}
}

View File

@@ -0,0 +1,223 @@
package org.fdroid.fdroid.views.repos
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.QrCode
import androidx.compose.material.primarySurface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.zxing.client.android.Intents.Scan.MIXED_SCAN
import com.google.zxing.client.android.Intents.Scan.SCAN_TYPE
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.journeyapps.barcodescanner.ScanOptions.QR_CODE
import org.fdroid.fdroid.R
import org.fdroid.fdroid.compose.ComposeUtils.FDroidButton
import org.fdroid.fdroid.compose.ComposeUtils.FDroidContent
import org.fdroid.fdroid.compose.ComposeUtils.FDroidOutlineButton
import org.fdroid.repo.AddRepoError
import org.fdroid.repo.AddRepoState
import org.fdroid.repo.Added
import org.fdroid.repo.Adding
import org.fdroid.repo.Fetching
import org.fdroid.repo.None
@Composable
fun AddRepoIntroScreen(
state: AddRepoState,
onFetchRepo: (String) -> Unit,
onAddRepo: () -> Unit,
onBackClicked: () -> Unit,
) {
Scaffold(topBar = {
TopAppBar(
elevation = 4.dp,
backgroundColor = MaterialTheme.colors.primarySurface,
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(Icons.Filled.ArrowBack, stringResource(R.string.back))
}
},
title = {
Text(
text = stringResource(R.string.repo_add_title),
modifier = Modifier.alpha(ContentAlpha.high),
)
},
)
}) { paddingValues ->
when (state) {
None -> AddRepoIntroContent(paddingValues, onFetchRepo)
is Fetching -> {
if (state.repo == null) {
RepoProgressScreen(paddingValues, stringResource(R.string.repo_state_fetching))
} else {
RepoPreviewScreen(paddingValues, state, onAddRepo)
}
}
Adding -> RepoProgressScreen(paddingValues, stringResource(R.string.repo_state_adding))
is Added -> Box(modifier = Modifier.padding(paddingValues)) // empty UI
is AddRepoError -> AddRepoErrorScreen(paddingValues, state)
}
}
}
@Composable
fun AddRepoIntroContent(paddingValues: PaddingValues, onFetchRepo: (String) -> Unit) {
Column(
verticalArrangement = spacedBy(16.dp),
horizontalAlignment = CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.padding(paddingValues),
) {
Text(
text = stringResource(R.string.repo_intro),
style = MaterialTheme.typography.body1,
)
val startForResult = rememberLauncherForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
onFetchRepo(result.contents)
}
}
FDroidButton(
"Scan QR code",
imageVector = Icons.Filled.QrCode,
onClick = {
startForResult.launch(ScanOptions().apply {
setPrompt("")
setBeepEnabled(true)
setOrientationLocked(false)
setDesiredBarcodeFormats(QR_CODE)
addExtra(SCAN_TYPE, MIXED_SCAN)
})
},
)
val isPreview = LocalInspectionMode.current
var manualExpanded by rememberSaveable { mutableStateOf(isPreview) }
Row(
horizontalArrangement = spacedBy(16.dp),
verticalAlignment = CenterVertically,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = ButtonDefaults.MinHeight)
.clickable { manualExpanded = !manualExpanded },
) {
Text(
text = stringResource(R.string.repo_enter_url)
)
Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = if (manualExpanded) {
Icons.Default.ArrowDropUp
} else {
Icons.Default.ArrowDropDown
},
contentDescription = null,
)
}
val textState = remember { mutableStateOf(TextFieldValue()) }
val focusRequester = remember { FocusRequester() }
AnimatedVisibility(visible = manualExpanded) {
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = spacedBy(16.dp),
) {
TextField(
value = textState.value,
minLines = 2,
onValueChange = { textState.value = it },
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester)
.onGloballyPositioned {
focusRequester.requestFocus()
},
)
Row(
horizontalArrangement = spacedBy(16.dp),
verticalAlignment = CenterVertically,
) {
val clipboardManager = LocalClipboardManager.current
FDroidOutlineButton(
"Paste",
imageVector = Icons.Default.ContentPaste,
onClick = {
if (clipboardManager.hasText()) {
textState.value =
TextFieldValue(clipboardManager.getText()?.text ?: "")
}
},
)
Spacer(modifier = Modifier.weight(1f))
FDroidButton(
text = stringResource(R.string.repo_add_add),
onClick = { onFetchRepo(textState.value.text) },
)
}
}
}
}
}
@Composable
@Preview
fun AddRepoIntroScreenPreview() {
FDroidContent {
AddRepoIntroScreen(None, {}, {}) {}
}
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_YES, widthDp = 720, heightDp = 360)
fun AddRepoIntroScreenPreviewNight() {
FDroidContent {
AddRepoIntroScreen(None, {}, {}) {}
}
}

View File

@@ -0,0 +1,262 @@
package org.fdroid.fdroid.views.repos
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Card
import androidx.compose.material.ContentAlpha
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Alignment.Companion.End
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
import androidx.core.content.res.ResourcesCompat.getDrawable
import androidx.core.os.LocaleListCompat
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import org.fdroid.database.MinimalApp
import org.fdroid.database.Repository
import org.fdroid.fdroid.FDroidApp
import org.fdroid.fdroid.R
import org.fdroid.fdroid.Utils
import org.fdroid.fdroid.Utils.getDownloadRequest
import org.fdroid.fdroid.compose.ComposeUtils.FDroidButton
import org.fdroid.fdroid.compose.ComposeUtils.FDroidContent
import org.fdroid.index.v2.FileV2
import org.fdroid.repo.FetchResult.IsExistingRepository
import org.fdroid.repo.FetchResult.IsNewMirror
import org.fdroid.repo.FetchResult.IsNewRepository
import org.fdroid.repo.Fetching
@Composable
fun RepoPreviewScreen(paddingValues: PaddingValues, state: Fetching, onAddRepo: () -> Unit) {
val isPreview = LocalInspectionMode.current
val localeList = LocaleListCompat.getDefault()
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = spacedBy(8.dp),
modifier = Modifier
.padding(paddingValues)
.fillMaxWidth(),
) {
item {
RepoPreviewHeader(state, onAddRepo, localeList, isPreview)
}
if (state.fetchResult == null || state.fetchResult is IsNewRepository) {
item {
Row(
verticalAlignment = CenterVertically,
horizontalArrangement = spacedBy(8.dp)
) {
Text(
text = "Included apps:",
style = MaterialTheme.typography.body1,
)
Text(
text = state.apps.size.toString(),
style = MaterialTheme.typography.body1,
)
if (!state.done) LinearProgressIndicator(modifier = Modifier.weight(1f))
}
}
items(items = state.apps, key = { it.packageName }) { app ->
RepoPreviewApp(state.repo ?: error("no repo"), app, localeList, isPreview)
}
}
}
}
@Composable
@OptIn(ExperimentalGlideComposeApi::class)
fun RepoPreviewHeader(
state: Fetching,
onAddRepo: () -> Unit,
localeList: LocaleListCompat,
isPreview: Boolean,
) {
Column(verticalArrangement = spacedBy(8.dp)) {
val repo = state.repo ?: error("repo was null")
val res = LocalContext.current.resources
Row(
horizontalArrangement = spacedBy(8.dp),
verticalAlignment = CenterVertically,
) {
if (isPreview) Image(
painter = rememberDrawablePainter(
getDrawable(res, R.drawable.ic_launcher, null)
),
contentDescription = null,
modifier = Modifier.size(48.dp),
) else GlideImage(
model = getDownloadRequest(repo, repo.getIcon(localeList)),
contentDescription = null,
modifier = Modifier.size(48.dp),
) {
it.fallback(R.drawable.ic_repo_app_default).error(R.drawable.ic_repo_app_default)
}
Column(horizontalAlignment = Alignment.Start) {
Text(
text = repo.getName(localeList) ?: "Unknown Repository",
maxLines = 1,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.body1,
)
Text(
text = repo.address.replaceFirst("https://", ""),
style = MaterialTheme.typography.body2,
modifier = Modifier.alpha(ContentAlpha.medium),
)
Text(
text = Utils.formatLastUpdated(res, repo.timestamp),
style = MaterialTheme.typography.body2,
)
}
}
if (state.canAdd) FDroidButton(
text = when (state.fetchResult) {
IsNewRepository -> stringResource(R.string.repo_add_new_title)
is IsNewMirror -> stringResource(R.string.repo_add_new_mirror)
else -> error("Unexpected fetch state: ${state.fetchResult}")
},
onClick = onAddRepo,
modifier = Modifier.align(End)
) else if (state.fetchResult is IsExistingRepository) {
Text(
text = stringResource(R.string.repo_exists),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.error,
)
}
val description = if (isPreview) {
LoremIpsum(42).values.joinToString(" ")
} else {
repo.getDescription(localeList)
}
if (description != null) Text(
text = description,
style = MaterialTheme.typography.body2,
)
}
}
@Composable
@OptIn(ExperimentalGlideComposeApi::class, ExperimentalFoundationApi::class)
fun LazyItemScope.RepoPreviewApp(
repo: Repository,
app: MinimalApp,
localeList: LocaleListCompat,
isPreview: Boolean,
) {
Card(
modifier = Modifier
.animateItemPlacement()
.fillMaxWidth(),
) {
Row(
horizontalArrangement = spacedBy(8.dp),
modifier = Modifier.padding(8.dp),
) {
if (isPreview) Image(
painter = rememberDrawablePainter(
getDrawable(LocalContext.current.resources, R.drawable.ic_launcher, null)
),
contentDescription = null,
modifier = Modifier.size(38.dp),
) else GlideImage(
model = getDownloadRequest(repo, app.getIcon(localeList)),
contentDescription = null,
modifier = Modifier.size(38.dp),
) {
it.fallback(R.drawable.ic_repo_app_default).error(R.drawable.ic_repo_app_default)
}
Column {
Text(
app.name ?: "Unknown app",
style = MaterialTheme.typography.body1,
)
Text(
app.summary ?: "",
style = MaterialTheme.typography.body2,
)
}
}
}
}
@Preview
@Composable
fun RepoPreviewScreenFetchingPreview() {
val repo = FDroidApp.createSwapRepo("https://example.org", "foo bar")
val app1 = object : MinimalApp {
override val repoId = 0L
override val packageName = "org.example"
override val name: String = "App 1 with a long name"
override val summary: String = "Summary of App1 which can also be a bit longer"
override fun getIcon(localeList: LocaleListCompat): FileV2? = null
}
val app2 = object : MinimalApp {
override val repoId = 0L
override val packageName = "com.example"
override val name: String = "App 2 with a name that is even longer than the first app"
override val summary: String =
"Summary of App2 which can also be a bit longer, even longer than other apps."
override fun getIcon(localeList: LocaleListCompat): FileV2? = null
}
val app3 = object : MinimalApp {
override val repoId = 0L
override val packageName = "net.example"
override val name: String = "App 3"
override val summary: String = "short summary"
override fun getIcon(localeList: LocaleListCompat): FileV2? = null
}
FDroidContent {
RepoPreviewScreen(
PaddingValues(0.dp),
Fetching(repo, listOf(app1, app2, app3), IsNewRepository)
) {}
}
}
@Preview
@Composable
fun RepoPreviewScreenNewMirrorPreview() {
val repo = FDroidApp.createSwapRepo("https://example.org", "foo bar")
FDroidContent {
RepoPreviewScreen(
PaddingValues(0.dp),
Fetching(repo, emptyList(), IsNewMirror(0L, "foo"))
) {}
}
}
@Preview
@Composable
fun RepoPreviewScreenExistingRepoPreview() {
val repo = FDroidApp.createSwapRepo("https://example.org", "foo bar")
FDroidContent {
RepoPreviewScreen(PaddingValues(0.dp), Fetching(repo, emptyList(), IsExistingRepository)) {}
}
}

View File

@@ -0,0 +1,46 @@
package org.fdroid.fdroid.views.repos
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.fdroid.fdroid.R
import org.fdroid.fdroid.compose.ComposeUtils.FDroidContent
@Composable
fun RepoProgressScreen(paddingValues: PaddingValues, text: String) {
Column(
verticalArrangement = spacedBy(16.dp, CenterVertically),
horizontalAlignment = CenterHorizontally,
modifier = Modifier
.padding(16.dp)
.padding(paddingValues)
.fillMaxSize(),
) {
Text(
text = text,
style = MaterialTheme.typography.h5,
)
CircularProgressIndicator(modifier = Modifier.size(64.dp))
}
}
@Preview
@Composable
fun FetchingRepoScreenPreview() {
FDroidContent {
RepoProgressScreen(PaddingValues(0.dp), stringResource(R.string.repo_state_fetching))
}
}

View File

@@ -171,8 +171,10 @@ This often occurs with apps installed via Google Play or other sources, if they
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="repo_add_title">Add new repository</string>
<string name="repo_add_new_title">Add repository</string>
<string name="repo_add_add">Add</string>
<string name="repo_add_mirror">Add mirror</string>
<string name="repo_add_new_mirror">Add as new mirror</string>
<string name="links">Links</string>
<string name="versions">Versions</string>
<string name="more">More</string>
@@ -190,13 +192,20 @@ This often occurs with apps installed via Google Play or other sources, if they
<string name="bluetooth_activity_not_found">No Bluetooth send method found, choose one!</string>
<string name="choose_bt_send">Choose Bluetooth send method</string>
<string name="repo_intro">A repository is an additional source of apps. Third-party ones you add here have different standards than those providing apps built by F-Droid itself.\n\nPlease ensure that the repository you are adding is trustworthy.</string>
<string name="repo_state_fetching">Fetching repository…</string>
<string name="repo_state_adding">Adding repository…</string>
<string name="repo_enter_url">Enter repository URL manually</string>
<string name="repo_add_url">Repository address</string>
<string name="repo_add_fingerprint">Fingerprint (optional)</string>
<string name="repo_exists">This repo was already added.</string>
<string name="repo_exists_add_fingerprint">%1$s is already setup, this will add new key information.</string>
<string name="repo_exists_enable">%1$s is already setup, confirm that you want to re-enable it.</string>
<string name="repo_exists_and_enabled">%1$s is already setup and enabled.</string>
<string name="repo_delete_to_overwrite">First delete %1$s in order to add this with a conflicting key.</string>
<string name="repo_exists_add_mirror">This is a copy of %1$s, add it as a mirror?</string>
<string name="repo_invalid">Invalid repository.\n\nContact the maintainer and let them know about the issue.</string>
<string name="repo_io_error">Error connecting to the repository.</string>
<string name="bad_fingerprint">Bad fingerprint</string>
<string name="invalid_url">This is not a valid URL.</string>
<string name="malformed_repo_uri">Ignoring malformed repo URI: %s</string>

View File

@@ -206,6 +206,16 @@
<sha256 value="7f08723ecabefba616d60c714b0e9a31301bd4d0792fcc7946c1479c57fd2d28" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity" version="1.7.2">
<artifact name="activity-1.7.2.aar">
<sha256 value="2a1abf8e0a598d246b1edcb71e2395e39f32332195ebe5ea40a3dd21355ba3ae" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-compose" version="1.7.2">
<artifact name="activity-compose-1.7.2.aar">
<sha256 value="c73db26b1672a63f144457271b49cc40380b5bafe60ed0710e82d0a774e36a88" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-ktx" version="1.2.2">
<artifact name="activity-ktx-1.2.2.aar">
<sha256 value="9829e13d6a6b045b03b21a330512e091dc76eb5b3ded0d88d1ab0509cf84a50e" origin="Generated by Gradle"/>
@@ -216,6 +226,16 @@
<sha256 value="1b46d678f1296d0308b7f1d1fd9c8b2688bd9f8fbddb2929db7f8e503105c68d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-ktx" version="1.7.0">
<artifact name="activity-ktx-1.7.0.aar">
<sha256 value="fce317d61a22f12967b475bfcb80c89dda66e418975e890ea703cb74e12b5b11" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-ktx" version="1.7.2">
<artifact name="activity-ktx-1.7.2.aar">
<sha256 value="b0b4206ece92919925061fdf5784dd21f0118534609e8f6d9404bdd0f5cb5a3d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.0.0">
<artifact name="annotation-1.0.0.jar">
<sha256 value="0baae9755f7caf52aa80cd04324b91ba93af55d4d1d17dcc9a7b53d99ef7c016" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -239,6 +259,11 @@
<sha256 value="97dc45afefe3a1e421da42b8b6e9f90491477c45fc6178203e3a5e8a05ee8553" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.5.0">
<artifact name="annotation-1.5.0.jar">
<sha256 value="261fb7c0210858500bab66d34354972a75166ab4182add283780b05513d6ec4a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.6.0">
<artifact name="annotation-metadata-1.6.0.jar">
<sha256 value="fbc64f5c44a7added8b6eab517cf7d70555e25153bf5d44a6ed9b0e5312f7de9" origin="Generated by Gradle"/>
@@ -387,6 +412,11 @@
<sha256 value="48167eeedc8da79c4d29deaf0d0cd9b5d8fedcae01f1a6efb3f28f08e8982f71" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.autofill" name="autofill" version="1.0.0">
<artifact name="autofill-1.0.0.aar">
<sha256 value="c9468f56e05006ea151a426c54957cd0799b8b83a579d2846dd22061f33e5ecd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.cardview" name="cardview" version="1.0.0">
<artifact name="cardview-1.0.0.aar">
<sha256 value="1193c04c22a3d6b5946dae9f4e8c59d6adde6a71b6bd5d87fb99d82dda1afec7" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -416,6 +446,106 @@
<sha256 value="2bfc54475c047131913361f56d0f7f019c6e5bee53eeb0eb7d94a7c499a05227" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.animation" name="animation" version="1.4.3">
<artifact name="animation-1.4.3.aar">
<sha256 value="bc00f44dad4c7b8dc7d73210797c9efacef6b029c8d0a46f50e203bbc9f0f9af" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.animation" name="animation-core" version="1.4.3">
<artifact name="animation-core-1.4.3.aar">
<sha256 value="d7d949d0a28699d7db5ae5769698255049400378bc8bc041265f85f6686fb2e4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.compiler" name="compiler" version="1.4.6">
<artifact name="compiler-1.4.6.jar">
<sha256 value="b10f529d9d83661e3484e173aa4b45775aa625ed7ea7564b6ab116ff65ad2d75" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.foundation" name="foundation" version="1.4.3">
<artifact name="foundation-1.4.3.aar">
<sha256 value="8316e9c745b534d957fe4e71198e22154267636e3176401dafec7a81599ecd84" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.foundation" name="foundation-layout" version="1.4.3">
<artifact name="foundation-layout-1.4.3.aar">
<sha256 value="978333f49bbe78d21e20b91a4cd800a0c5eead9876e267e076332b1a87a11c5e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.material" name="material" version="1.4.3">
<artifact name="material-1.4.3.aar">
<sha256 value="2d9b66b4b19c461293d355906d7cfff161dd155adf7f837323f1b7a4ffd10ca8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.material" name="material-icons-core" version="1.4.3">
<artifact name="material-icons-core-1.4.3.aar">
<sha256 value="5ef4030ae792e46fa6866d32c7554347b51afd5820c20c2cd2448d5dd9d20c7b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.material" name="material-icons-extended" version="1.4.3">
<artifact name="material-icons-extended-1.4.3.aar">
<sha256 value="895ebb8838e04c219481db3bdf773a46a19a2684c2a280d3a9ef57e62c28e180" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.material" name="material-ripple" version="1.4.3">
<artifact name="material-ripple-1.4.3.aar">
<sha256 value="34e3319f8c3646f000ecb1d6e10ee950d63a6b9d5f92178470669418c1b1ac32" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.runtime" name="runtime" version="1.4.3">
<artifact name="runtime-1.4.3.aar">
<sha256 value="f975185e13d3e5b0c142e220e924bfe643d4421b0fdae3f2036f1e0160a390f4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.runtime" name="runtime-saveable" version="1.4.3">
<artifact name="runtime-saveable-1.4.3.aar">
<sha256 value="756d7f53d64b664ad168c7109aa88cc394fb51aee6a2bb7ac86982242834f5ca" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui" version="1.4.3">
<artifact name="ui-1.4.3.aar">
<sha256 value="a49acd04ac5d596a1e3b00fd965612901b7d05975c51cc5bcf5258bfc3dbca43" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-geometry" version="1.4.3">
<artifact name="ui-geometry-1.4.3.aar">
<sha256 value="ef98ec03f6104730c3152f8ea27404f0764c715872f25a1d893879dd4c2e55bd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-graphics" version="1.4.3">
<artifact name="ui-graphics-1.4.3.aar">
<sha256 value="298cdc2ff4ea610a22b01f666a460239aa260f2467f7762d575d84e773545b2d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-text" version="1.4.3">
<artifact name="ui-text-1.4.3.aar">
<sha256 value="dfa801c2b6b29c97a7f6a20fc45a9b2cb8da4d52b1601d9f10c70878f98f1faa" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-tooling" version="1.4.3">
<artifact name="ui-tooling-1.4.3.aar">
<sha256 value="e641617b76204f9a03771ee40455553d5ec23d0c1f4a49d1e8177553769b6c06" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-tooling-data" version="1.4.3">
<artifact name="ui-tooling-data-1.4.3.aar">
<sha256 value="9b9a8d446f1f2bd12157329aba7751755118a659f02cbd7d40afba9ad1e7a61e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-tooling-preview" version="1.4.3">
<artifact name="ui-tooling-preview-1.4.3.aar">
<sha256 value="7e6316f9439671bc5bd732df93fd3c41433be487f749acae52308101dc8670f7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-unit" version="1.4.3">
<artifact name="ui-unit-1.4.3.aar">
<sha256 value="0dfbac9fbf2ca6dce36cd3d990786c9bc09fcfe962cbe27af531311d49611eb4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose.ui" name="ui-util" version="1.4.3">
<artifact name="ui-util-1.4.3.aar">
<sha256 value="a6ad526f26de348c69f57f1bd8742853ea58b3a22a571fbb76a6d8ba8e620d7e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.concurrent" name="concurrent-futures" version="1.0.0">
<artifact name="concurrent-futures-1.0.0.jar">
<sha256 value="5595a40e278a7b39fa78a09490e3d7f3faa95c7b01447148bd38b5ade0605c35" origin="Generated by Gradle"/>
@@ -656,6 +786,11 @@
<sha256 value="f31a06c150ecb03073f55a6f7b0b74a240a6a8d727c14ce76726d020570dfa8c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.emoji2" name="emoji2" version="1.3.0">
<artifact name="emoji2-1.3.0.aar">
<sha256 value="2bf23818b23a996ddaa1b5fd5bb32129daff6bbb2dce15166e2fccdd2010b1a5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.emoji2" name="emoji2-views-helper" version="1.0.0">
<artifact name="emoji2-views-helper-1.0.0.aar">
<sha256 value="39616a3d66551fc18585112ce1d12a14ac0fb588a89a6528cd97842da6221ec2" origin="Generated by Gradle"/>
@@ -666,6 +801,11 @@
<sha256 value="7ffa4d464d9db259fca0cdb50fbd4ab63d6872bcda59468b9f7555504c7d5ac4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.emoji2" name="emoji2-views-helper" version="1.3.0">
<artifact name="emoji2-views-helper-1.3.0.aar">
<sha256 value="9a1351295a4f739df0efe8344adaa9afb34856c3af584d4a9afbec105a45b90b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.exifinterface" name="exifinterface" version="1.2.0">
<artifact name="exifinterface-1.2.0.aar">
<sha256 value="aae68e513095e475a7670556eacba772ec2bb592d17187091578d3fef947aea7" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -752,6 +892,11 @@
<sha256 value="04d525073469214d0c99e81aaa875dd548ba32b82945abd8326bc50229df700d" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.1.0">
<artifact name="lifecycle-common-2.1.0.jar">
<sha256 value="76db6be533bd730fb361c2feb12a2c26d9952824746847da82601ef81f082643" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.2.0">
<artifact name="lifecycle-common-2.2.0.jar">
<sha256 value="63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -790,6 +935,11 @@
<sha256 value="f34831b6c71cd844e1d35d1be49d5e79447c5ab856346531b1e8676fda7374b1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common-java8" version="2.6.1">
<artifact name="lifecycle-common-java8-2.6.1.jar">
<sha256 value="c6deada2fac53b8ea6523dbda77597b128006674616f140f04df23264c6d1aa3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.0.0">
<artifact name="lifecycle-livedata-2.0.0.aar">
<sha256 value="c82609ced8c498f0a701a30fb6771bb7480860daee84d82e0a81ee86edf7ba39" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -823,6 +973,11 @@
<sha256 value="67359f609dfc2bf65da1270b23033f856064ec279f058e0a70c715f7c9003031" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.1.0">
<artifact name="lifecycle-livedata-core-2.1.0.aar">
<sha256 value="a150743e86c30eddf1660ad454b1f86041efdefcd1a039320c4c26db87f7119a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.2.0">
<artifact name="lifecycle-livedata-core-2.2.0.aar">
<sha256 value="556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -1000,6 +1155,11 @@
<sha256 value="e4ff4338999e1c6c9c724719f5d4aa7dd61bf6f545d5256a27a9d375df9f2330" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-compose" version="2.6.1">
<artifact name="lifecycle-viewmodel-compose-2.6.1.aar">
<sha256 value="c2b820204c2ca58ef9cb015a3e5ffbe88d8c55b09e5cb851d62b445528a379e0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-ktx" version="2.3.1">
<artifact name="lifecycle-viewmodel-ktx-2.3.1.aar">
<sha256 value="5fb3591b6a54eeb3e204be0125d48eb987b8ea45a5048140036865482ccf9de9" origin="Generated by Gradle"/>
@@ -1371,6 +1531,11 @@
<sha256 value="671284e62e393f16ceae1a99a3a9a07bf1aacda29f8fe7b6b884355ef34c09cf" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="androidx.test" name="core-ktx" version="1.4.0">
<artifact name="core-ktx-1.4.0.aar">
<sha256 value="e4f9ca2b8f700cc278d878ed3925730e1ad5d60135bbfd05ab6708a528ebfa58" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.test" name="monitor" version="1.2.0">
<artifact name="monitor-1.2.0.aar">
<sha256 value="fc97ca3f00f8ca30b7d5167fbd8736756048e2cc4f8e92dc891106751a5baeef" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -3210,6 +3375,11 @@
<sha256 value="315b1325283c3d0cf9bc0599c1ecdb85e5f7863b1aa25991b63d616b13930cb6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.bumptech.glide" name="compose" version="1.0.0-alpha.1">
<artifact name="compose-1.0.0-alpha.1.aar">
<sha256 value="ce66e14da56220e17fc0be51789e2f299b64e25303441fb5759b49007f8f9dac" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.bumptech.glide" name="disklrucache" version="4.12.0">
<artifact name="disklrucache-4.12.0.jar">
<sha256 value="6f0cc9069646ac582ee78dbf621e90303ea200b9ce41c20ac368e925ed456379" origin="Generated by Gradle"/>
@@ -3240,6 +3410,16 @@
<sha256 value="ec32c33f5b289fd7b0a54485e27392f896b239cefd533385e262de1530190c3f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.bumptech.glide" name="ktx" version="1.0.0-alpha.1">
<artifact name="ktx-1.0.0-alpha.1.aar">
<sha256 value="4748150712e03253bdc5cdf2e0ee061ab7063f89ec8bcef126514da7b0602198" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.bumptech.glide" name="recyclerview-integration" version="4.14.2">
<artifact name="recyclerview-integration-4.14.2.aar">
<sha256 value="eaad945bb20c6b89114aa20c5c7be40f04072c4eb3970c309752ded9e1195f7f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.gundy" name="semver4j" version="0.16.4">
<artifact name="semver4j-0.16.4-nodeps.jar">
<pgp value="55e770230e69cc6de143fb5b62c82e50836eb3ee"/>
@@ -3294,6 +3474,21 @@
<sha256 value="cd6db17a11a31ede794ccbd1df0e4d9750f640234731f21cff885a9997277e81" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
<component group="com.google.accompanist" name="accompanist-drawablepainter" version="0.25.1">
<artifact name="accompanist-drawablepainter-0.25.1.aar">
<sha256 value="57ed676b60b0f7d72b051613dc3d7ceb0295a90bcdca7c9316ff33dbff8ab33a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.accompanist" name="accompanist-themeadapter-core" version="0.30.1">
<artifact name="accompanist-themeadapter-core-0.30.1.aar">
<sha256 value="8df37ee7b51516e4b2640bb02e87326e89b5d0fcd5d566fdd040697aa5a17302" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.accompanist" name="accompanist-themeadapter-material" version="0.30.1">
<artifact name="accompanist-themeadapter-material-0.30.1.aar">
<sha256 value="4b36a29bc2cbc50dcd41421fc50a61d60df61533cbdd2225c965e6a2ea3f9ab9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.google.android" name="annotations" version="4.1.1.4">
<artifact name="annotations-4.1.1.4.jar">
<pgp value="0f07d1201bddab67cfb84eb479752db6c966f0b8"/>
@@ -3991,6 +4186,11 @@
<sha256 value="b29c1c21e52ed6238cd3fed39d880a17ecf2360118604548cea8821be6801e1c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.journeyapps" name="zxing-android-embedded" version="4.3.0">
<artifact name="zxing-android-embedded-4.3.0.aar">
<sha256 value="4ab03353127c34e55cbb80fbc9a34031decbcfb1ec5e8f991944d2d494621a33" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.linkedin.dexmaker" name="dexmaker" version="2.28.1">
<artifact name="dexmaker-2.28.1.jar">
<pgp value="8df3b0aa23ed78be5233f6c2dea3d207428ef16d"/>
@@ -8499,6 +8699,11 @@
<sha256 value="8f3fffcfde2b725da91ebf21d8282eea5a29e04659454edd1054b34419f04031" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-common" version="1.5.30">
<artifact name="kotlin-stdlib-common-1.5.30.jar">
<sha256 value="8eb3ac530a978422e0f8d0bba78ba2c628bec997dc2f1aa4ef8c5b854e3764b8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-common" version="1.5.31">
<artifact name="kotlin-stdlib-common-1.5.31.jar">
<sha256 value="dfa2a18e26b028388ee1968d199bf6f166f737ab7049c25a5e2da614404e22ad" origin="Generated by Gradle"/>
@@ -8989,6 +9194,11 @@
<sha256 value="51bc74326401b4d3fa4e784f89c51c7932ee52998b619271e88f175dead82242" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="atomicfu" version="0.16.1">
<artifact name="atomicfu-metadata-0.16.1-all.jar">
<sha256 value="9e3c37269f9d4ffc3edbd4f667e42fe27e92b06bb98e6815da6f19e635275654" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="atomicfu" version="0.16.3">
<artifact name="atomicfu-metadata-0.16.3-all.jar">
<sha256 value="c3c0546dd33e0ef75782734d9703eb3583a28414b1d4464fcbde56d70a7d5a6d" origin="Generated by Gradle because artifact wasn't signed"/>
@@ -9058,6 +9268,11 @@
<sha256 value="f36ea75c31934bfad0682cfc435cce922e28b3bffa5af26cf86f07db13008f8a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-android" version="1.5.0">
<artifact name="kotlinx-coroutines-android-1.5.0.jar">
<sha256 value="7099198391d673c199fea084423d9f3fdc79470acba19111330c7f88504279c7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-android" version="1.5.2">
<artifact name="kotlinx-coroutines-android-1.5.2.jar">
<sha256 value="86cf9892b0bd5306a8f4d7ad8a82356f614dc7d519eb3063b0887d7c2b405928" origin="Generated by Gradle"/>
@@ -9098,6 +9313,11 @@
<sha256 value="f8c8b7485d4a575e38e5e94945539d1d4eccd3228a199e1a9aa094e8c26174ee" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core" version="1.5.0">
<artifact name="kotlinx-coroutines-core-metadata-1.5.0-all.jar">
<sha256 value="fd58f72f025aa044b09b8b18299012f0d5710632834bcfab3aee32d3b1f26a88" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core" version="1.5.2">
<artifact name="kotlinx-coroutines-core-metadata-1.5.2-all.jar">
<sha256 value="4d19a1c1c82bd973d034644f4ffa3d5355cb61bd34575aff86cc609e0e41d6e1" origin="Generated by Gradle because artifact wasn't signed"/>