Skip to content

Commit

Permalink
Merge pull request #51 from m-i-n-a-r/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
m-i-n-a-r authored Jan 30, 2023
2 parents f4d37c1 + cd1134e commit 757e8d0
Show file tree
Hide file tree
Showing 41 changed files with 1,578 additions and 119 deletions.
85 changes: 50 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,56 @@ An open source app to choose randomly between numbers, answers, options and so o
</p>

## Introduction
This is an open source app created to experiment some new android features, but it's also available on Play Store and F-Droid for all users.
The main purpose of the app is provide a **random choice** in different ways. The app contains some basic personalization options and an introduction, plus a lot of **animated vector drawables**. The design is a bit personal, but it mostly follows the guidelines.

This is an open source app created to experiment some new android features, but it's also available
on Play Store and F-Droid for all users.
The main purpose of the app is provide a **random choice** in different ways. The app contains some
basic personalization options and an introduction, plus a lot of **animated vector drawables**. The
design is a bit personal, but it mostly follows the guidelines.

## Translations (refer to [Crowdin](https://crwd.in/randomix))

| LANGUAGE | SPECIAL THANKS |
|:----------------------|-------------------------:|
| **English** | myself, cosmojg |
| **Italian** | myself |
| **Spanish** | myself |
| **German** | deep-ocean-fish, julius-d|
| **Russian** | BValeo |
| **French** | Firokat |
| **Portuguese** | Kiskadee-dev |
| **Brazilian** | Kiskadee-dev |
| **Czech** | Miloš Koliáš, mormegil-cz|
| **Simplified Chinese**| pumguy |
| **Indonesian** | the7thNightmare |
| **Chinese (Taiwan)** | Still34 |
| **Asturian** | Softastur |
| **English (GB)** | SecularSteve |
| **Dutch** | SecularSteve |
| **Bosnian** | SecularSteve |
| **Croatian** | SecularSteve |
| **Serbian** | SecularSteve |
| **Serbian (latin)** | SecularSteve |
| LANGUAGE | SPECIAL THANKS |
|:-----------------------|--------------------------:|
| **English** | myself, cosmojg |
| **Italian** | myself |
| **Spanish** | myself |
| **German** | deep-ocean-fish, julius-d |
| **Russian** | BValeo |
| **French** | Firokat |
| **Portuguese** | Kiskadee-dev |
| **Brazilian** | Kiskadee-dev |
| **Czech** | Miloš Koliáš, mormegil-cz |
| **Simplified Chinese** | pumguy |
| **Indonesian** | the7thNightmare |
| **Chinese (Taiwan)** | Still34 |
| **Asturian** | Softastur |
| **English (GB)** | SecularSteve |
| **Dutch** | SecularSteve |
| **Bosnian** | SecularSteve |
| **Croatian** | SecularSteve |
| **Serbian** | SecularSteve |
| **Serbian (latin)** | SecularSteve |

Special thanks to Nickoriginal for the overall improvement of each translation

## Features

- Every tab in the bottom navigation bar contains a type of random choice. The available types are:
- **Roulette** -> chooses between a specified number of options or a custom number range, entered from the user. Includes a list of recent options easy to select, pin, delete or restore.
- **Coin** -> simply flips a coin and prints the result.
- **Dice** -> throws a chosen number of dice and prints the result.
- **Magic Ball** -> provides randomly chosen answers to any question.
- **Roulette** -> chooses between a specified number of options or a custom number range,
entered from the user. Includes a list of recent options easy to select, pin, delete or
restore.
- **Coin** -> simply flips a coin and prints the result.
- **Dice** -> throws a chosen number of dice and prints the result.
- **Magic Ball** -> provides randomly chosen answers to any question.
- Light and dark themes (Automatic dark mode supported)
- Full Material You support
- Selectable accent (no app restart needed)
- Up to 10 dice, 3v3 mode
- The app remember the last used mode
- Roulette presets for letters and numbers
- Custom ranges (values from 0 to 99999)
- Customizable magic ball answers
- Multiwindows support
- Android 12 animated splashscreen supported
- Android 13 per-app language support
Expand All @@ -64,8 +72,9 @@ Special thanks to Nickoriginal for the overall improvement of each translation
- Available in many languages (see the table above)

## Screenshots

<p align='center'>
<img src='https://i.imgur.com/C61kjuo.png' width='22%'/>
<img src='https://i.imgur.com/tDc3Tb2.jpg' width='22%'/>
<img src='https://i.imgur.com/FCe38ow.png' width='22%'/>
<img src='https://i.imgur.com/vr4tYQ9.png' width='22%'/>
<img src='https://i.imgur.com/0PZaSPX.png' width='22%'/>
Expand All @@ -76,20 +85,26 @@ Special thanks to Nickoriginal for the overall improvement of each translation
</p>

## Download
The app is now available through Google Play and F-Droid.

The app is now available through Google Play and F-Droid.

[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/com.minar.randomix/)
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/com.minar.randomix/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.minar.randomix)
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.minar.randomix)

## Credits and contributions

Randomix uses an open source library:

- [App Intro](https://github.com/AppIntro/AppIntro)

Currently, Randomix supports the languages in the above table. If you want to translate the app in any other language or update an existing translation, just contact me or send a pull request: you'll be quoted both on Github and in the Play Store description.
Currently, Randomix supports the languages in the above table. If you want to translate the app in
any other language or update an existing translation, just contact me or send a pull request: you'll
be quoted both on Github and in the Play Store description.

This app was written during my free time as a training. It was first published on June 1, 2018. Many good devs have helped me understanding the best practices and they taught me a lot of useful tricks. A special thank to every contributor. and God bless Stack Overflow.
This app was written during my free time as a training. It was first published on June 1, 2018. Many
good devs have helped me understanding the best practices and they taught me a lot of useful tricks.
A special thank to every contributor. and God bless Stack Overflow.
8 changes: 4 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ android {
applicationId "com.minar.randomix"
minSdkVersion 23
targetSdkVersion 33
versionCode 39
versionName "2.4"
resConfigs 'en', 'ast', 'cs', 'de', 'es', 'fr', 'id', 'it', 'ru', 'pt', 'pt-rBR', 'zh-rCN', 'zh-rTW'
versionCode 40
versionName "2.5"
resConfigs 'en', 'en-rGB', 'ast', 'bs', 'cs', 'de', 'es', 'fr', 'hr', 'id', 'it', 'nl', 'ru', 'pt', 'pt-rBR', 'sr', 'sr-rCS', 'zh-rCN', 'zh-rTW'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
Expand All @@ -29,7 +29,7 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.android.material:material:1.8.0-rc01'
implementation 'com.google.android.material:material:1.9.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.navigation:navigation-fragment:2.5.3"
implementation "androidx.navigation:navigation-ui:2.5.3"
Expand Down
4 changes: 2 additions & 2 deletions app/release/output-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 38,
"versionName": "2.3",
"versionCode": 39,
"versionName": "2.4",
"outputFile": "app-release.apk"
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package com.minar.randomix.fragments;

import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SwitchCompat;

import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.textfield.TextInputLayout;
import com.minar.randomix.R;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MagicBallBottomSheet extends BottomSheetDialogFragment {
private final MagicBallFragment magicBall;
private SharedPreferences sp;
private EditText answerText;
private final List<String> loadedAnswers = new ArrayList<>();
private ChipGroup answerChips;
private TextView placeholder;
private SwitchCompat customAnswersSwitch;

MagicBallBottomSheet(MagicBallFragment magicBall) {
this.magicBall = magicBall;
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Inflate the bottom sheet, initialize the shared preferences and the recent options list
View v = inflater.inflate(R.layout.magic_ball_bottom_sheet, container, false);
sp = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean customActive = sp.getBoolean("custom_answers_active", false);
String customAnswers = sp.getString("custom_answers", "");
loadedAnswers.clear();
Collections.addAll(loadedAnswers, customAnswers.split(";"));
loadedAnswers.remove(""); // Ensure no empty elements are saved
placeholder = v.findViewById(R.id.customAnswersEmptyPlaceholder);

// Animate the drawable in loop
ImageView noRecentImage = v.findViewById(R.id.recentImage);
Drawable animatedNoRecent = noRecentImage.getDrawable();
if (animatedNoRecent instanceof Animatable2) {
((Animatable2) animatedNoRecent).registerAnimationCallback(new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
((Animatable2) animatedNoRecent).start();
}
});
((Animatable2) animatedNoRecent).start();
}

// Manage the text field
answerChips = v.findViewById(R.id.customAnswerChipGroup);
answerText = v.findViewById(R.id.customAnswerText);
TextInputLayout answerTextLayout = v.findViewById(R.id.customAnswerTextLayout);
answerTextLayout.setEndIconOnClickListener(v1 -> {
insertAnswerChip(""); // Empty answer = take it from the text field
});
answerText.setOnEditorActionListener(((v1, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_SEND) {
// Insert in both the list and the layout
insertAnswerChip("");
return true;
}
return false;
}));

// Manage the switch
customAnswersSwitch = v.findViewById(R.id.customAnswerSwitch);
customAnswersSwitch.setChecked(customActive);
customAnswersSwitch.setEnabled(loadedAnswers.size() >= 3);
customAnswersSwitch.setOnCheckedChangeListener((buttonView, checked) -> {
if (checked) {
magicBall.setCustomAnswers(loadedAnswers.toArray(new String[0]));
} else {
magicBall.setCustomAnswers(null);
}
});

// Manage placeholder and chips
managePlaceholder();
if (!loadedAnswers.isEmpty()) {
for (String s : loadedAnswers) {
insertAnswerChip(s);
}
}

return v;
}

// Insert the chip for a custom answer
private void insertAnswerChip(String answer) {
String currentAnswer;
if (!answer.isEmpty()) currentAnswer = answer;
else {
currentAnswer = answerText.getText().toString().trim();
currentAnswer = currentAnswer.replaceAll("\\s+", " ");
answerText.setText("");
}

// Check if the limit is reached
if (loadedAnswers.size() > 100 || currentAnswer.isEmpty() || currentAnswer.equals(" ")) {
return;
}

// Add to the list if the answer is not empty
if (answer.isEmpty()) {
loadedAnswers.add(currentAnswer);
}
if (loadedAnswers.size() > 2) {
customAnswersSwitch.setEnabled(true);
if (customAnswersSwitch.isChecked())
magicBall.setCustomAnswers(loadedAnswers.toArray(new String[0]));
}
managePlaceholder();

// Inflate the layout and its onclick action
LayoutInflater inflater = LayoutInflater.from(getActivity());
final Chip chip = (Chip) inflater.inflate(R.layout.custom_chip, answerChips, false);
chip.setText(currentAnswer);
chip.setId(loadedAnswers.size());

// Add the chip with an animation
answerChips.addView(chip);
Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.chip_enter_anim);
chip.startAnimation(animation);

// Remove the chip and the element from the list
chip.setOnClickListener(view -> removeChip(chip));
}

// Remove a single chip
private void removeChip(final Chip chip) {
// Remove the chip with an animation
if (chip == null) return;
loadedAnswers.remove(chip.getText().toString());
if (loadedAnswers.size() < 2) {
customAnswersSwitch.setEnabled(false);
magicBall.setCustomAnswers(null);
}
final Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.chip_exit_anim);
chip.startAnimation(animation);
managePlaceholder();
chip.postDelayed(() -> answerChips.removeView(chip), 400);
}

// Evaluate the visibility of the placeholder
private void managePlaceholder() {
if (loadedAnswers.size() == 0) {
answerChips.setVisibility(View.GONE);
placeholder.setVisibility(View.VISIBLE);
} else {
answerChips.setVisibility(View.VISIBLE);
placeholder.setVisibility(View.GONE);
}
}

@Override
public void onDismiss(@NonNull DialogInterface dialog) {
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("custom_answers_active", customAnswersSwitch.isChecked());
StringBuilder answersString = new StringBuilder();
for (String s : loadedAnswers) {
String answer = s.replace(";", "");
answersString.append(answer).append(";");
}
editor.putString("custom_answers", answersString.toString());

// Set the custom answers to make sure they are updated
if (customAnswersSwitch.isChecked() && customAnswersSwitch.isEnabled())
magicBall.setCustomAnswers(loadedAnswers.toArray(new String[0]));
else magicBall.setCustomAnswers(null);

editor.apply();
super.onDismiss(dialog);
}
}
Loading

0 comments on commit 757e8d0

Please sign in to comment.