Code cleanups (#3264)

* Kotlin 1.8.10

https://github.com/JetBrains/kotlin/releases/tag/v1.8.10

* Migrate onActivityCreated to onViewCreated

* More final modifiers

* Java Cleanups

* Kotlin cleanups

* More final modifiers

* Const value TOOLBAR_HIDE_DELAY_MS

* Revert
This commit is contained in:
Goooler 2023-02-21 02:58:37 +08:00 committed by GitHub
parent 0baf3fe467
commit cfea5700b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 123 additions and 133 deletions

View File

@ -239,8 +239,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
}
if (permissionsToRequest.isEmpty()) {
int[] permissionsAlreadyGranted = new int[permissions.length];
for (int i = 0; i < permissionsAlreadyGranted.length; ++i)
permissionsAlreadyGranted[i] = PackageManager.PERMISSION_GRANTED;
requester.onRequestPermissionsResult(permissions, permissionsAlreadyGranted);
return;
}

View File

@ -113,7 +113,6 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
lifecycleScope.launch {
viewModel.events.collect { event ->
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
when (event) {
Event.CREATE_ERROR -> showMessage(R.string.error_create_list)
Event.RENAME_ERROR -> showMessage(R.string.error_rename_list)

View File

@ -67,7 +67,7 @@ import java.util.List;
import at.connyduck.sparkbutton.helpers.Utils;
public class NotificationsAdapter extends RecyclerView.Adapter {
public class NotificationsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public interface AdapterDataSource<T> {
int getItemCount();
@ -87,12 +87,12 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private static final InputFilter[] COLLAPSE_INPUT_FILTER = new InputFilter[]{SmartLengthInputFilter.INSTANCE};
private static final InputFilter[] NO_INPUT_FILTER = new InputFilter[0];
private String accountId;
private final String accountId;
private StatusDisplayOptions statusDisplayOptions;
private StatusActionListener statusListener;
private NotificationActionListener notificationActionListener;
private AccountActionListener accountActionListener;
private AdapterDataSource<NotificationViewData> dataSource;
private final StatusActionListener statusListener;
private final NotificationActionListener notificationActionListener;
private final AccountActionListener accountActionListener;
private final AdapterDataSource<NotificationViewData> dataSource;
private final AbsoluteTimeFormatter absoluteTimeFormatter = new AbsoluteTimeFormatter();
public NotificationsAdapter(String accountId,
@ -164,11 +164,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @NonNull List payloads) {
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @NonNull List<Object> payloads) {
bindViewHolder(viewHolder, position, payloads);
}
private void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @Nullable List payloads) {
private void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @Nullable List<Object> payloads) {
Object payloadForHolder = payloads != null && !payloads.isEmpty() ? payloads.get(0) : null;
if (position < this.dataSource.getItemCount()) {
NotificationViewData notification = dataSource.getItemAt(position);
@ -234,7 +234,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
concreteNotification.getId());
} else {
if (payloadForHolder instanceof List)
for (Object item : (List) payloadForHolder) {
for (Object item : (List<?>) payloadForHolder) {
if (StatusBaseViewHolder.Key.KEY_CREATED.equals(item) && statusViewData != null) {
holder.setCreatedAt(statusViewData.getStatus().getActionableStatus().getCreatedAt());
}
@ -353,11 +353,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
}
private static class FollowViewHolder extends RecyclerView.ViewHolder {
private TextView message;
private TextView usernameView;
private TextView displayNameView;
private ImageView avatar;
private StatusDisplayOptions statusDisplayOptions;
private final TextView message;
private final TextView usernameView;
private final TextView displayNameView;
private final ImageView avatar;
private final StatusDisplayOptions statusDisplayOptions;
FollowViewHolder(View itemView, StatusDisplayOptions statusDisplayOptions) {
super(itemView);
@ -414,7 +414,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private final TextView contentWarningDescriptionTextView;
private final Button contentWarningButton;
private final Button contentCollapseButton; // TODO: This code SHOULD be based on StatusBaseViewHolder
private StatusDisplayOptions statusDisplayOptions;
private final StatusDisplayOptions statusDisplayOptions;
private final AbsoluteTimeFormatter absoluteTimeFormatter;
private String accountId;
@ -422,9 +422,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
private NotificationActionListener notificationActionListener;
private StatusViewData.Concrete statusViewData;
private int avatarRadius48dp;
private int avatarRadius36dp;
private int avatarRadius24dp;
private final int avatarRadius48dp;
private final int avatarRadius36dp;
private final int avatarRadius24dp;
StatusNotificationViewHolder(
View itemView,

View File

@ -25,7 +25,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.core.view.ViewKt;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -76,46 +75,46 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
public static final String KEY_CREATED = "created";
}
private TextView displayName;
private TextView username;
private ImageButton replyButton;
private TextView replyCountLabel;
private SparkButton reblogButton;
private SparkButton favouriteButton;
private SparkButton bookmarkButton;
private ImageButton moreButton;
private ConstraintLayout mediaContainer;
protected MediaPreviewLayout mediaPreview;
private TextView sensitiveMediaWarning;
private View sensitiveMediaShow;
protected TextView[] mediaLabels;
protected CharSequence[] mediaDescriptions;
private MaterialButton contentWarningButton;
private ImageView avatarInset;
private final TextView displayName;
private final TextView username;
private final ImageButton replyButton;
private final TextView replyCountLabel;
private final SparkButton reblogButton;
private final SparkButton favouriteButton;
private final SparkButton bookmarkButton;
private final ImageButton moreButton;
private final ConstraintLayout mediaContainer;
protected final MediaPreviewLayout mediaPreview;
private final TextView sensitiveMediaWarning;
private final View sensitiveMediaShow;
protected final TextView[] mediaLabels;
protected final CharSequence[] mediaDescriptions;
private final MaterialButton contentWarningButton;
private final ImageView avatarInset;
public ImageView avatar;
public TextView metaInfo;
public TextView content;
public TextView contentWarningDescription;
public final ImageView avatar;
public final TextView metaInfo;
public final TextView content;
public final TextView contentWarningDescription;
private RecyclerView pollOptions;
private TextView pollDescription;
private Button pollButton;
private final RecyclerView pollOptions;
private final TextView pollDescription;
private final Button pollButton;
private LinearLayout cardView;
private LinearLayout cardInfo;
private ShapeableImageView cardImage;
private TextView cardTitle;
private TextView cardDescription;
private TextView cardUrl;
private PollAdapter pollAdapter;
private final LinearLayout cardView;
private final LinearLayout cardInfo;
private final ShapeableImageView cardImage;
private final TextView cardTitle;
private final TextView cardDescription;
private final TextView cardUrl;
private final PollAdapter pollAdapter;
private final NumberFormat numberFormat = NumberFormat.getNumberInstance();
private final AbsoluteTimeFormatter absoluteTimeFormatter = new AbsoluteTimeFormatter();
protected int avatarRadius48dp;
private int avatarRadius36dp;
private int avatarRadius24dp;
protected final int avatarRadius48dp;
private final int avatarRadius36dp;
private final int avatarRadius24dp;
private final Drawable mediaPreviewUnloaded;
@ -325,8 +324,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} else {
long then = createdAt.getTime();
long now = System.currentTimeMillis();
String readout = TimestampUtils.getRelativeTimeSpanString(metaInfo.getContext(), then, now);
timestampText = readout;
timestampText = TimestampUtils.getRelativeTimeSpanString(metaInfo.getContext(), then, now);
}
}
@ -598,9 +596,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
final String accountId,
final String statusContent,
StatusDisplayOptions statusDisplayOptions) {
View.OnClickListener profileButtonClickListener = button -> {
listener.onViewAccount(accountId);
};
View.OnClickListener profileButtonClickListener = button -> listener.onViewAccount(accountId);
avatar.setOnClickListener(profileButtonClickListener);
displayName.setOnClickListener(profileButtonClickListener);

View File

@ -44,8 +44,8 @@ public class StatusViewHolder extends StatusBaseViewHolder {
private static final InputFilter[] COLLAPSE_INPUT_FILTER = new InputFilter[]{SmartLengthInputFilter.INSTANCE};
private static final InputFilter[] NO_INPUT_FILTER = new InputFilter[0];
private TextView statusInfo;
private Button contentCollapseButton;
private final TextView statusInfo;
private final Button contentCollapseButton;
public StatusViewHolder(View itemView) {
super(itemView);

View File

@ -941,13 +941,13 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
}
private fun getFullUsername(account: Account): String {
if (account.isRemote()) {
return "@" + account.username
return if (account.isRemote()) {
"@" + account.username
} else {
val localUsername = account.localUsername
// Note: !! here will crash if this pane is ever shown to a logged-out user. With AccountActivity this is believed to be impossible.
val domain = accountManager.activeAccount!!.domain
return "@$localUsername@$domain"
"@$localUsername@$domain"
}
}

View File

@ -697,7 +697,7 @@ class ComposeActivity :
var oneMediaWithoutDescription = false
for (media in viewModel.media.value) {
if (media.description == null || media.description.isEmpty()) {
if (media.description.isNullOrEmpty()) {
oneMediaWithoutDescription = true
break
}

View File

@ -70,9 +70,7 @@ class FocusIndicatorView
if (event.actionMasked == MotionEvent.ACTION_CANCEL)
return false
val imageSize = this.imageSize
if (imageSize == null)
return false
val imageSize = this.imageSize ?: return false
// Convert touch xy to point inside image
focus = Attachment.Focus(axisToFocus(event.x, imageSize.x, this.width), -axisToFocus(event.y, imageSize.y, this.height))

View File

@ -44,7 +44,7 @@ import javax.inject.Inject
class DraftHelper @Inject constructor(
val context: Context,
val okHttpClient: OkHttpClient,
private val okHttpClient: OkHttpClient,
db: AppDatabase
) {
@ -140,7 +140,7 @@ class DraftHelper @Inject constructor(
}
}
suspend fun deleteDraftAndAttachments(draft: DraftEntity) {
private suspend fun deleteDraftAndAttachments(draft: DraftEntity) {
deleteAttachments(draft)
draftDao.delete(draft.id)
}

View File

@ -33,7 +33,7 @@ class DraftsViewModel @Inject constructor(
val database: AppDatabase,
val accountManager: AccountManager,
val api: MastodonApi,
val draftHelper: DraftHelper
private val draftHelper: DraftHelper
) : ViewModel() {
val drafts = Pager(

View File

@ -55,8 +55,8 @@ class ProxyPreferencesFragment : PreferenceFragmentCompat() {
val portErrorMessage = getString(
R.string.pref_title_http_proxy_port_message,
ProxyConfiguration.MIN_PROXY_PORT,
ProxyConfiguration.MAX_PROXY_PORT
MIN_PROXY_PORT,
MAX_PROXY_PORT
)
validatedEditTextPreference(portErrorMessage, ProxyConfiguration::isValidProxyPort) {

View File

@ -55,7 +55,7 @@ abstract class TimelineViewModel(
private val api: MastodonApi,
private val eventHub: EventHub,
protected val accountManager: AccountManager,
protected val sharedPreferences: SharedPreferences,
private val sharedPreferences: SharedPreferences,
private val filterModel: FilterModel
) : ViewModel() {
@ -69,7 +69,7 @@ abstract class TimelineViewModel(
private set
protected var alwaysShowSensitiveMedia = false
protected var alwaysOpenSpoilers = false
private var alwaysOpenSpoilers = false
private var filterRemoveReplies = false
private var filterRemoveReblogs = false
protected var readingOrder: ReadingOrder = ReadingOrder.OLDEST_FIRST

View File

@ -256,7 +256,7 @@ class ViewThreadFragment : SFragment(), OnRefreshListener, StatusActionListener,
* When started the job will wait `delayMs` then show `view`. If the job is cancelled at
* any time `view` is hidden.
*/
@CheckResult()
@CheckResult
private fun getProgressBarJob(view: View, delayMs: Long) = viewLifecycleOwner.lifecycleScope.launch(
start = CoroutineStart.LAZY
) {

View File

@ -44,7 +44,7 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
@Inject
lateinit var accountManager: AccountManager
public fun <T> observeInContext(context: T, showAlert: Boolean) where T : Context, T : LifecycleOwner {
fun <T> observeInContext(context: T, showAlert: Boolean) where T : Context, T : LifecycleOwner {
accountManager.activeAccount?.let { activeAccount ->
val coroutineScope = context.lifecycleScope
@ -63,7 +63,7 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
AlertDialog.Builder(context)
.setTitle(R.string.action_post_failed)
.setMessage(
context.getResources().getQuantityString(R.plurals.action_post_failed_detail, count)
context.resources.getQuantityString(R.plurals.action_post_failed_detail, count)
)
.setPositiveButton(R.string.action_post_failed_show_drafts) { _: DialogInterface?, _: Int ->
clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts
@ -78,7 +78,7 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
}
}
} else {
draftsNeedUserAlert.observe(context) { _ ->
draftsNeedUserAlert.observe(context) {
Log.d(TAG, "User id $activeAccountId: Clean out notification-worthy drafts")
clearDraftsAlert(coroutineScope, activeAccountId)
}
@ -91,7 +91,7 @@ class DraftsAlert @Inject constructor(db: AppDatabase) {
/**
* Clear drafts alert for specified user
*/
fun clearDraftsAlert(coroutineScope: LifecycleCoroutineScope, id: Long) {
private fun clearDraftsAlert(coroutineScope: LifecycleCoroutineScope, id: Long) {
coroutineScope.launch {
draftDao.draftsClearNeedUserAlert(id)
}

View File

@ -40,7 +40,7 @@ class ViewModelFactory @Inject constructor(private val viewModels: MutableMap<Cl
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

View File

@ -137,7 +137,7 @@ data class Status(
)
}
fun getEditableText(): String {
private fun getEditableText(): String {
val contentSpanned = content.parseAsMastodonHtml()
val builder = SpannableStringBuilder(content.parseAsMastodonHtml())
for (span in contentSpanned.getSpans(0, content.length, URLSpan::class.java)) {

View File

@ -172,20 +172,20 @@ public class NotificationsFragment extends SFragment implements
// Each element is either a Notification for loading data or a Placeholder
private final PairedList<Either<Placeholder, Notification>, NotificationViewData> notifications
= new PairedList<>(new Function<Either<Placeholder, Notification>, NotificationViewData>() {
= new PairedList<>(new Function<>() {
@Override
public NotificationViewData apply(Either<Placeholder, Notification> input) {
if (input.isRight()) {
Notification notification = input.asRight()
.rewriteToStatusTypeIfNeeded(accountManager.getActiveAccount().getAccountId());
.rewriteToStatusTypeIfNeeded(accountManager.getActiveAccount().getAccountId());
boolean sensitiveStatus = notification.getStatus() != null && notification.getStatus().getActionableStatus().getSensitive();
return ViewDataUtils.notificationToViewData(
notification,
alwaysShowSensitiveMedia || !sensitiveStatus,
alwaysOpenSpoiler,
true
notification,
alwaysShowSensitiveMedia || !sensitiveStatus,
alwaysOpenSpoiler,
true
);
} else {
return new NotificationViewData.Placeholder(input.asLeft().id, false);
@ -311,8 +311,8 @@ public class NotificationsFragment extends SFragment implements
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Activity activity = getActivity();
if (activity == null) throw new AssertionError("Activity is null");
@ -344,7 +344,7 @@ public class NotificationsFragment extends SFragment implements
}
@Override
public void onLoadMore(int totalItemsCount, RecyclerView view) {
public void onLoadMore(int totalItemsCount, @NonNull RecyclerView view) {
NotificationsFragment.this.onLoadMore();
}
};
@ -352,23 +352,23 @@ public class NotificationsFragment extends SFragment implements
binding.recyclerView.addOnScrollListener(scrollListener);
eventHub.getEvents()
.observeOn(AndroidSchedulers.mainThread())
.to(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
.subscribe(event -> {
if (event instanceof FavoriteEvent) {
setFavouriteForStatus(((FavoriteEvent) event).getStatusId(), ((FavoriteEvent) event).getFavourite());
} else if (event instanceof BookmarkEvent) {
setBookmarkForStatus(((BookmarkEvent) event).getStatusId(), ((BookmarkEvent) event).getBookmark());
} else if (event instanceof ReblogEvent) {
setReblogForStatus(((ReblogEvent) event).getStatusId(), ((ReblogEvent) event).getReblog());
} else if (event instanceof PinEvent) {
setPinForStatus(((PinEvent) event).getStatusId(), ((PinEvent) event).getPinned());
} else if (event instanceof BlockEvent) {
removeAllByAccountId(((BlockEvent) event).getAccountId());
} else if (event instanceof PreferenceChangedEvent) {
onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey());
}
});
.observeOn(AndroidSchedulers.mainThread())
.to(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
.subscribe(event -> {
if (event instanceof FavoriteEvent) {
setFavouriteForStatus(((FavoriteEvent) event).getStatusId(), ((FavoriteEvent) event).getFavourite());
} else if (event instanceof BookmarkEvent) {
setBookmarkForStatus(((BookmarkEvent) event).getStatusId(), ((BookmarkEvent) event).getBookmark());
} else if (event instanceof ReblogEvent) {
setReblogForStatus(((ReblogEvent) event).getStatusId(), ((ReblogEvent) event).getReblog());
} else if (event instanceof PinEvent) {
setPinForStatus(((PinEvent) event).getStatusId(), ((PinEvent) event).getPinned());
} else if (event instanceof BlockEvent) {
removeAllByAccountId(((BlockEvent) event).getAccountId());
} else if (event instanceof PreferenceChangedEvent) {
onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey());
}
});
}
@Override
@ -483,7 +483,6 @@ public class NotificationsFragment extends SFragment implements
Notification notification = notifications.get(position).asRight();
Status status = notification.getStatus();
if (status == null) return;
;
super.viewThread(status.getActionableId(), status.getActionableStatus().getUrl());
}
@ -1171,20 +1170,20 @@ public class NotificationsFragment extends SFragment implements
new AsyncDifferConfig.Builder<>(diffCallback).build());
private final NotificationsAdapter.AdapterDataSource<NotificationViewData> dataSource =
new NotificationsAdapter.AdapterDataSource<NotificationViewData>() {
@Override
public int getItemCount() {
return differ.getCurrentList().size();
}
new NotificationsAdapter.AdapterDataSource<>() {
@Override
public int getItemCount() {
return differ.getCurrentList().size();
}
@Override
public NotificationViewData getItemAt(int pos) {
return differ.getCurrentList().get(pos);
}
};
@Override
public NotificationViewData getItemAt(int pos) {
return differ.getCurrentList().get(pos);
}
};
private static final DiffUtil.ItemCallback<NotificationViewData> diffCallback
= new DiffUtil.ItemCallback<NotificationViewData>() {
= new DiffUtil.ItemCallback<>() {
@Override
public boolean areItemsTheSame(NotificationViewData oldItem, NotificationViewData newItem) {

View File

@ -57,10 +57,13 @@ class ViewVideoFragment : ViewMediaFragment() {
mediaController.hide()
}
private lateinit var mediaActivity: ViewMediaActivity
private val TOOLBAR_HIDE_DELAY_MS = 3000L
private lateinit var mediaController: MediaController
private var isAudio = false
companion object {
private const val TOOLBAR_HIDE_DELAY_MS = 3000L
}
override fun onAttach(context: Context) {
super.onAttach(context)
videoActionsListener = context as VideoActionsListener
@ -188,10 +191,8 @@ class ViewVideoFragment : ViewMediaFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val attachment = arguments?.getParcelable<Attachment>(ARG_ATTACHMENT)
?: throw IllegalArgumentException("attachment has to be set")
if (attachment == null) {
throw IllegalArgumentException("attachment has to be set")
}
val url = attachment.url
isAudio = attachment.type == Attachment.Type.AUDIO

View File

@ -38,7 +38,7 @@ class ListStatusAccessibilityDelegate(
private val context: Context get() = recyclerView.context
private val itemDelegate = object : RecyclerViewAccessibilityDelegate.ItemDelegate(this) {
private val itemDelegate = object : ItemDelegate(this) {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfoCompat

View File

@ -30,7 +30,7 @@ val Locale.modernLanguageCode: String
fun Locale.getTuskyDisplayName(context: Context): String {
return context.getString(
R.string.language_display_name_format,
this?.displayLanguage,
this?.getDisplayLanguage(this)
displayLanguage,
getDisplayLanguage(this)
)
}

View File

@ -6,7 +6,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
open class RxAwareViewModel : ViewModel() {
val disposables = CompositeDisposable()
private val disposables = CompositeDisposable()
fun Disposable.autoDispose() = disposables.add(this)

View File

@ -147,12 +147,12 @@ fun highlightSpans(text: Spannable, colour: Int) {
val length = text.length
var start = 0
var end = 0
while (end >= 0 && end < length && start >= 0) {
while (end in 0 until length && start >= 0) {
// Search for url first because it can contain the other characters
val found = findPattern(string, end)
start = found.start
end = found.end
if (start >= 0 && end > start) {
if (start in 0 until end) {
text.setSpan(getSpan(found.matchType, string, colour, start, end), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
start += finders[found.matchType]!!.searchPrefixWidth
}

View File

@ -237,7 +237,6 @@ class BottomSheetActivityTest {
init {
mastodonApi = api
@Suppress("UNCHECKED_CAST")
bottomSheet = mock()
}

View File

@ -30,7 +30,7 @@ filemoji-compat = "3.2.7"
glide = "4.13.2"
glide-animation-plugin = "2.23.0"
gson = "2.9.0"
kotlin = "1.7.10"
kotlin = "1.8.10"
image-cropper = "4.3.1"
lifecycle = "2.5.1"
material = "1.6.1"