Rename to Foxi Settings, drop cloud sync, move support/sponsors into Donate

- Rename 'Neko Settings' -> 'Foxi Settings' (en/ru)
- Remove the cloud settings sync action (was broken/unneeded)
- Move 'Support the project' (t.me/vpnghostbot) and 'Top sponsors' from the
  main settings list into the 'Support FoxiGram' (Donate) screen
- Remove the Google Play billing section from the Donate screen
This commit is contained in:
instant992 2026-06-09 10:17:54 +04:00
parent 47f9aef160
commit e9e9711f0b
4 changed files with 14 additions and 180 deletions

View file

@ -3,154 +3,59 @@ package tw.nekomimi.nekogram.settings;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Outline;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.PendingPurchasesParams;
import com.android.billingclient.api.ProductDetails;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.QueryProductDetailsParams;
import com.google.common.collect.ImmutableList;
import com.google.zxing.EncodeHintType;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.ui.ActionBar.AlertDialog;
import org.telegram.messenger.browser.Browser;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.BottomSheet;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Components.BulletinFactory;
import org.telegram.ui.Components.FlickerLoadingView;
import org.telegram.ui.Components.ItemOptions;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.TextHelper;
import org.telegram.ui.Components.UItem;
import org.telegram.ui.Components.UniversalAdapter;
import org.telegram.ui.LaunchActivity;
import org.telegram.ui.Stories.recorder.ButtonWithCounterView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import tw.nekomimi.nekogram.helpers.remote.ConfigHelper;
public class NekoDonateActivity extends BaseNekoSettingsActivity implements PurchasesUpdatedListener {
private static final List<String> SKUS = Arrays.asList("donate001", "donate002", "donate005", "donate010", "donate020", "donate050", "donate100");
public class NekoDonateActivity extends BaseNekoSettingsActivity {
private final List<ConfigHelper.Crypto> cryptos = ConfigHelper.getCryptos();
private final int donateRow = 100;
private final int supportProjectRow = 1;
private final int topSponsorsRow = 2;
private final int cryptoRow = 200;
private BillingClient billingClient;
private List<ProductDetails> productDetails;
@Override
public boolean onFragmentCreate() {
super.onFragmentCreate();
billingClient = BillingClient.newBuilder(ApplicationLoader.applicationContext)
.setListener(this)
.enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
.build();
return true;
}
@Override
public void onFragmentDestroy() {
super.onFragmentDestroy();
billingClient.endConnection();
}
private void showErrorAlert(BillingResult result) {
if (getParentActivity() == null || result.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED || result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
return;
}
AndroidUtilities.runOnUIThread(() -> {
if (TextUtils.isEmpty(result.getDebugMessage())) {
BulletinFactory.of(this).createErrorBulletin(LocaleController.getString(R.string.ErrorOccurred) + ": " + result.getResponseCode()).show();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity(), resourcesProvider);
builder.setTitle(LocaleController.getString(R.string.ErrorOccurred));
builder.setMessage(result.getDebugMessage());
builder.setPositiveButton(LocaleController.getString(R.string.OK), null);
showDialog(builder.create());
}
});
}
@Override
public View createView(Context context) {
View fragmentView = super.createView(context);
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingServiceDisconnected() {
}
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
var productList =
SKUS.stream().map(s -> QueryProductDetailsParams.Product.newBuilder()
.setProductId(s)
.setProductType(BillingClient.ProductType.INAPP)
.build())
.collect(Collectors.toList());
var params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
.build();
billingClient.queryProductDetailsAsync(params, (queryResult, list) -> {
if (queryResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (!list.isEmpty()) {
AndroidUtilities.runOnUIThread(() -> {
productDetails = list;
if (listView != null) {
listView.adapter.update(true);
}
});
}
} else {
showErrorAlert(queryResult);
}
});
} else {
showErrorAlert(billingResult);
}
}
});
return fragmentView;
}
@Override
protected void fillItems(ArrayList<UItem> items, UniversalAdapter adapter) {
items.add(UItem.asButtonSubtext(supportProjectRow, R.drawable.msg_input_like, LocaleController.getString(R.string.FoxSupportProject), LocaleController.getString(R.string.FoxSupportProjectAbout)));
items.add(UItem.asButtonSubtext(topSponsorsRow, R.drawable.msg_premium_liststar, LocaleController.getString(R.string.FoxTopSponsors), LocaleController.getString(R.string.FoxTopSponsorsAbout)));
items.add(UItem.asShadow(null));
if (cryptos != null && !cryptos.isEmpty()) {
items.add(UItem.asHeader(LocaleController.getString(R.string.Cryptocurrency)));
var cryptoId = 0;
@ -159,36 +64,15 @@ public class NekoDonateActivity extends BaseNekoSettingsActivity implements Purc
}
items.add(UItem.asShadow(null));
}
items.add(UItem.asHeader(LocaleController.getString(R.string.GooglePlay)));
if (productDetails != null && !productDetails.isEmpty()) {
for (int i = 0; i < productDetails.size(); i++) {
var product = productDetails.get(i);
var details = product.getOneTimePurchaseOfferDetails();
items.add(TextSettingsCellFactory.of(donateRow + i, details != null ? details.getFormattedPrice() : product.getName()));
}
} else {
items.add(UItem.asFlicker(1, FlickerLoadingView.TEXT_SETTINGS_TYPE));
}
items.add(UItem.asShadow(null));
}
@Override
protected void onItemClick(UItem item, View view, int position, float x, float y) {
var id = item.id;
if (id >= donateRow && id < cryptoRow) {
if (productDetails != null && productDetails.size() > id - donateRow) {
var productDetailsParamsList =
ImmutableList.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails.get(id - donateRow))
.build()
);
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build();
billingClient.launchBillingFlow(getParentActivity(), flowParams);
}
if (id == supportProjectRow) {
Browser.openUrl(getParentActivity(), "https://t.me/vpnghostbot");
} else if (id == topSponsorsRow) {
presentFragment(new FoxSponsorsActivity());
} else if (id >= cryptoRow) {
ConfigHelper.Crypto crypto = cryptos.get(id - cryptoRow);
QRCodeBottomSheet.showForCrypto(this, crypto);
@ -219,37 +103,6 @@ public class NekoDonateActivity extends BaseNekoSettingsActivity implements Purc
return LocaleController.getString(R.string.Donate);
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
for (Purchase purchase : list) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
ConsumeParams params = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.consumeAsync(params, (billingResult1, s) -> {
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
AndroidUtilities.runOnUIThread(() -> {
BulletinFactory.of(this).createErrorBulletin(LocaleController.getString(R.string.DonateThankYou)).show();
try {
fragmentView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
} catch (Exception ignored) {
}
if (getParentActivity() instanceof LaunchActivity) {
((LaunchActivity) getParentActivity()).getFireworksOverlay().start();
}
});
} else {
showErrorAlert(billingResult1);
}
});
}
}
} else {
showErrorAlert(billingResult);
}
}
public static class QRCodeBottomSheet extends BottomSheet {
private QRCodeBottomSheet(Context context, ConfigHelper.Crypto crypto, Theme.ResourcesProvider resourcesProvider) {

View file

@ -29,7 +29,6 @@ import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.Cells.SettingsSearchCell;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.FragmentFloatingButton;
import org.telegram.ui.Components.ItemOptions;
import org.telegram.ui.Components.LayoutHelper;
import org.telegram.ui.Components.UItem;
@ -43,7 +42,6 @@ import java.util.Locale;
import me.vkryl.android.animator.BoolAnimator;
import me.vkryl.android.animator.FactorAnimator;
import tw.nekomimi.nekogram.accessibility.AccessibilitySettingsActivity;
import tw.nekomimi.nekogram.helpers.CloudSettingsHelper;
import tw.nekomimi.nekogram.helpers.PasscodeHelper;
import tw.nekomimi.nekogram.helpers.remote.ConfigHelper;
@ -68,12 +66,9 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
private final int sourceCodeRow = rowId++;
private final int translationRow = rowId++;
private final int donateRow = rowId++;
private final int supportProjectRow = rowId++;
private final int topSponsorsRow = rowId++;
private final int sponsorRow = 100;
private ActionBarMenuItem syncItem;
private final ArrayList<SearchResult> searchArray = createSearchArray();
private final ArrayList<CharSequence> resultNames = new ArrayList<>();
private final ArrayList<SearchResult> searchResults = new ArrayList<>();
@ -138,9 +133,6 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
search(editText.getText().toString());
}
});
syncItem = menu.addItem(1, R.drawable.cloud_sync);
syncItem.setContentDescription(LocaleController.getString(R.string.CloudConfig));
syncItem.setOnClickListener(v -> CloudSettingsHelper.getInstance().showDialog(this));
return fragmentView;
}
@ -180,10 +172,6 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
items.add(UItem.asButtonSubtext(donateRow, R.drawable.msg_input_like, LocaleController.getString(R.string.Donate), LocaleController.getString(R.string.DonateAbout)).slug("donate"));
items.add(UItem.asShadow(null));
items.add(UItem.asButtonSubtext(supportProjectRow, R.drawable.msg_input_like, LocaleController.getString(R.string.FoxSupportProject), LocaleController.getString(R.string.FoxSupportProjectAbout)).slug("supportProject"));
items.add(UItem.asButtonSubtext(topSponsorsRow, R.drawable.msg_premium_liststar, LocaleController.getString(R.string.FoxTopSponsors), LocaleController.getString(R.string.FoxTopSponsorsAbout)).slug("topSponsors"));
items.add(UItem.asShadow(null));
newsList.clear();
newsList.addAll(ConfigHelper.getNewsForSettings());
if (!newsList.isEmpty()) {
@ -220,10 +208,6 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
getMessagesController().openByUserName(LocaleController.getString(R.string.OfficialChannelUsername), this, 1);
} else if (id == donateRow) {
presentFragment(new NekoDonateActivity());
} else if (id == supportProjectRow) {
Browser.openUrl(getParentActivity(), "https://t.me/vpnghostbot");
} else if (id == topSponsorsRow) {
presentFragment(new FoxSponsorsActivity());
} else if (id == translationRow) {
Browser.openUrl(getParentActivity(), "https://neko.crowdin.com/nekogram");
} else if (id == websiteRow) {
@ -268,9 +252,6 @@ public class NekoSettingsActivity extends BaseNekoSettingsActivity implements Fa
@Override
public void onFactorChanged(int id, float factor, float fraction, FactorAnimator callee) {
if (id == ANIMATOR_ID_SEARCH_PAGE_VISIBLE) {
FragmentFloatingButton.setAnimatedVisibility(syncItem, 1f - factor);
}
}
@Override

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="NekoSettings">Настройки Neko</string>
<string name="NekoSettings">Настройки Foxi</string>
<string name="PreferIPv6">Предпочитать подключение через IPv6</string>
<string name="MessageMenu">Меню сообщений</string>
<string name="HidePhone">Скрыть мой номер телефона</string>

View file

@ -3,7 +3,7 @@
<string name="AppNameNeko" translatable="false">@string/Nekogram</string>
<string name="Nekogram" translatable="false">FoxiGram</string>
<string name="NekogramBeta" translatable="false">FoxiGram Beta</string>
<string name="NekoSettings">Neko Settings</string>
<string name="NekoSettings">Foxi Settings</string>
<string name="PreferIPv6">Prefer connecting through IPv6</string>
<string name="MessageMenu">Message menu</string>
<string name="HidePhone">Hide my phone number</string>