build: change package name to dev.nulldori.eamemu
This commit is contained in:
parent
2fc95d18c7
commit
eadd41f1d7
@ -98,14 +98,13 @@ android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
namespace "tk.nulldori.eamemu"
|
||||
namespace "dev.nulldori.eamemu"
|
||||
defaultConfig {
|
||||
applicationId "tk.nulldori.eamemu"
|
||||
applicationId "dev.nulldori.eamemu"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 220
|
||||
versionName "2.2.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionCode 300
|
||||
versionName "3.0.0"
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
@ -172,8 +171,6 @@ dependencies {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
||||
// for react-native-fs
|
||||
implementation project(':react-native-fs')
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
import android.content.Context;
|
||||
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||
import com.facebook.flipper.android.utils.FlipperUtils;
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu
|
||||
package dev.nulldori.eamemu
|
||||
|
||||
import java.io.UnsupportedEncodingException
|
||||
import kotlin.experimental.xor
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu
|
||||
package dev.nulldori.eamemu
|
||||
|
||||
class B(arg4: ByteArray) {
|
||||
private val k: IntArray
|
@ -1,20 +1,10 @@
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import tk.nulldori.eamemu.A;
|
||||
|
||||
public class CardConvModule extends ReactContextBaseJavaModule {
|
||||
private static ReactApplicationContext reactContext;
|
||||
private static A converter;
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu
|
||||
package dev.nulldori.eamemu
|
||||
|
||||
object E {
|
||||
fun a(arg4: CharSequence): ByteArray {
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@ -50,9 +50,9 @@ public class HcefModule extends ReactContextBaseJavaModule implements LifecycleE
|
||||
isHceFSupport = true;
|
||||
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(getReactApplicationContext());
|
||||
if(nfcAdapter != null){
|
||||
if(nfcAdapter != null && nfcAdapter.isEnabled()){
|
||||
nfcFCardEmulation = NfcFCardEmulation.getInstance(nfcAdapter);
|
||||
componentName = new ComponentName("tk.nulldori.eamemu","tk.nulldori.eamemu.eAMEMuService");
|
||||
componentName = new ComponentName("dev.nulldori.eamemu","dev.nulldori.eamemu.eAMEMuService");
|
||||
if(nfcFCardEmulation != null){
|
||||
nfcFCardEmulation.registerSystemCodeForService(componentName, "4000");
|
||||
isHceFEnabled = true;
|
||||
@ -74,52 +74,23 @@ public class HcefModule extends ReactContextBaseJavaModule implements LifecycleE
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
void setSID(String sid, Promise promise){
|
||||
void enableService(String sid, Promise promise){
|
||||
if(nfcFCardEmulation == null || componentName == null){
|
||||
promise.reject("NULL_ERROR", "nfcFCardEmulation or componentName is null");
|
||||
return ;
|
||||
}
|
||||
|
||||
sid = sid.toUpperCase();
|
||||
|
||||
if(sid.length() != 16)
|
||||
promise.reject("LENGTH_ERROR", "The length of sid must be 16");
|
||||
if(sid.matches("[0-9a-fA-F]+") == false)
|
||||
promise.reject("HEX_ERROR", "SID must be 16-digit hex number");
|
||||
if(sid.substring(0,4).contentEquals("02FE") == false)
|
||||
promise.reject("PREFIX_ERROR", "SID must be start with 02FE");
|
||||
|
||||
boolean result = nfcFCardEmulation.setNfcid2ForService(componentName, sid);
|
||||
|
||||
if (result) {
|
||||
promise.resolve(true);
|
||||
} else {
|
||||
promise.reject("FAIL", "setNfcid2ForService returned false");
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
void enableService(Promise promise){
|
||||
if(nfcFCardEmulation == null || componentName == null){
|
||||
promise.reject("NULL_ERROR", "nfcFCardEmulation or componentName is null");
|
||||
if (!nfcFCardEmulation.setNfcid2ForService(componentName, sid)) {
|
||||
promise.reject("SET_NFCID2_FAIL", "setNfcid2ForService returned false");
|
||||
return ;
|
||||
}
|
||||
|
||||
String cardId = nfcFCardEmulation.getNfcid2ForService(componentName);
|
||||
|
||||
if(cardId.length() != 16)
|
||||
promise.reject("LENGTH_ERROR", "The length of sid must be 16");
|
||||
if(cardId.matches("[0-9a-fA-F]+") == false)
|
||||
promise.reject("HEX_ERROR", "SID must be 16-digit hex number");
|
||||
if(cardId.substring(0,4).contentEquals("02FE") == false)
|
||||
promise.reject("PREFIX_ERROR", "SID must be start with 02FE");
|
||||
|
||||
boolean result = nfcFCardEmulation.enableService(getCurrentActivity(), componentName);
|
||||
|
||||
if (result) {
|
||||
nowUsing = true;
|
||||
promise.resolve(true);
|
||||
} else {
|
||||
if (!nfcFCardEmulation.enableService(getCurrentActivity(), componentName)) {
|
||||
promise.reject("FAIL", "enableService returned false");
|
||||
}
|
||||
|
||||
nowUsing = true;
|
||||
promise.resolve(true);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
@ -128,14 +99,12 @@ public class HcefModule extends ReactContextBaseJavaModule implements LifecycleE
|
||||
promise.reject("NULL_ERROR", "nfcFCardEmulation or componentName is null");
|
||||
}
|
||||
|
||||
boolean result = nfcFCardEmulation.disableService(getCurrentActivity());
|
||||
|
||||
if (result) {
|
||||
nowUsing = false;
|
||||
promise.resolve(true);
|
||||
} else {
|
||||
if (!nfcFCardEmulation.disableService(getCurrentActivity())) {
|
||||
promise.reject("FAIL", "disableService returned false");
|
||||
}
|
||||
|
||||
nowUsing = false;
|
||||
promise.resolve(true);
|
||||
}
|
||||
|
||||
@Override
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
|
||||
import android.os.Bundle;
|
||||
import com.facebook.react.ReactActivity;
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
|
||||
import android.app.Application;
|
||||
import com.facebook.react.PackageList;
|
||||
@ -8,7 +8,7 @@ import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.rnfs.RNFSPackage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
@ -1,4 +1,4 @@
|
||||
package tk.nulldori.eamemu;
|
||||
package dev.nulldori.eamemu;
|
||||
|
||||
import android.nfc.cardemulation.HostNfcFService;
|
||||
import android.os.Bundle;
|
@ -4,7 +4,7 @@
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.rndiffapp;
|
||||
package dev.nulldori.eamemu;
|
||||
import android.content.Context;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
/**
|
@ -1,604 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StatusBar,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
Dimensions,
|
||||
ImageBackground,
|
||||
findNodeHandle,
|
||||
Alert,
|
||||
StyleSheet,
|
||||
Image,
|
||||
KeyboardAvoidingView,
|
||||
TextInput,
|
||||
} from 'react-native';
|
||||
import update from 'react-addons-update';
|
||||
import Icon from 'react-native-vector-icons/MaterialIcons';
|
||||
import ImagePicker from 'react-native-image-crop-picker';
|
||||
import CardConv from '../modules/CardConv';
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
class CardPreview extends React.Component {
|
||||
render() {
|
||||
let cardContent = (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.3)',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<TouchableOpacity style={{ flex: 1 }}>
|
||||
<Text
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 20,
|
||||
left: 20,
|
||||
fontSize: 17,
|
||||
fontWeight: 'bold',
|
||||
color: '#ffffff',
|
||||
}}
|
||||
>
|
||||
{this.props.name}
|
||||
</Text>
|
||||
<View style={{ flex: 1, justifyContent: 'center', paddingTop: 20 }}>
|
||||
<Text
|
||||
style={{
|
||||
paddingTop: 0,
|
||||
textAlign: 'center',
|
||||
alignSelf: 'center',
|
||||
color: '#E0E0E0',
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{this.props.uid
|
||||
? this.props.uid.substr(0, 4) +
|
||||
'-' +
|
||||
this.props.uid.substr(4, 4) +
|
||||
'-' +
|
||||
this.props.uid.substr(8, 4) +
|
||||
'-' +
|
||||
this.props.uid.substr(12, 4)
|
||||
: ''}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
paddingTop: 8,
|
||||
textAlign: 'center',
|
||||
alignSelf: 'center',
|
||||
fontSize: 24,
|
||||
color: '#FAFAFA',
|
||||
fontWeight: '500',
|
||||
letterSpacing: -0.5,
|
||||
}}
|
||||
>
|
||||
{i18n.t('card_touch_to_enable')}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={{ height: 1, backgroundColor: '#FAFAFA' }} />
|
||||
<View style={{ height: 48, flexDirection: 'row' }}>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 14, color: '#FAFAFA' }}>
|
||||
{i18n.t('card_edit')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={{ width: 1, backgroundColor: '#FAFAFA' }} />
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 14, color: '#ffffff' }}>
|
||||
{i18n.t('card_delete')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
borderRadius: 8,
|
||||
height: this.props.cardHeight,
|
||||
marginTop: 16,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
]}
|
||||
>
|
||||
{this.props.image ? (
|
||||
<ImageBackground
|
||||
source={{ uri: this.props.image }}
|
||||
style={{
|
||||
flex: 1,
|
||||
resizeMode: 'contain',
|
||||
}}
|
||||
blurRadius={2}
|
||||
borderRadius={8}
|
||||
>
|
||||
{cardContent}
|
||||
</ImageBackground>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
resizeMode: 'contain',
|
||||
backgroundColor: '#03A9F4',
|
||||
}}
|
||||
blurRadius={2}
|
||||
borderRadius={8}
|
||||
>
|
||||
{cardContent}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ETextInput extends React.Component {
|
||||
state = {
|
||||
focused: false,
|
||||
value: this.props.value ?? '',
|
||||
};
|
||||
|
||||
onFocusCallback() {
|
||||
this.setState({ focused: true });
|
||||
if (typeof this.props.onFocus === 'function') {
|
||||
this.props.onFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onBlurCallback() {
|
||||
this.setState({ focused: false });
|
||||
if (typeof this.props.onBlur === 'function') {
|
||||
this.props.onBlur();
|
||||
}
|
||||
}
|
||||
|
||||
onChangeTextCallback(text) {
|
||||
if (typeof this.props.filter === 'function') {
|
||||
text = this.props.filter(text);
|
||||
}
|
||||
if (typeof this.props.onChangeText === 'function') {
|
||||
this.props.onChangeText(text);
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.textInput.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={this.props.style}>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
fontSize: 14,
|
||||
color: this.props.error
|
||||
? '#F44336'
|
||||
: this.state.focused
|
||||
? '#03A9F4'
|
||||
: '#9E9E9E',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
this.props.titleStyle,
|
||||
]}
|
||||
>
|
||||
{this.props.title}
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
style={[{ fontSize: 17, paddingTop: 4 }, this.props.textStyle]}
|
||||
value={this.props.value}
|
||||
onFocus={() => this.onFocusCallback()}
|
||||
onBlur={() => this.onBlurCallback()}
|
||||
onChangeText={text => this.onChangeTextCallback(text)}
|
||||
editable={this.props.editable}
|
||||
maxLength={this.props.maxLength}
|
||||
autoCapitalize={this.props.autoCapitalize}
|
||||
ref={ref => (this.textInput = ref)}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
paddingTop: 2,
|
||||
height: this.state.focused ? 2 : 1,
|
||||
backgroundColor: this.props.error
|
||||
? '#F44336'
|
||||
: this.state.focused
|
||||
? '#03A9F4'
|
||||
: '#9E9E9E',
|
||||
opacity: this.props.editable ? 1 : 0.5,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CardEditScreen extends React.Component {
|
||||
state = {
|
||||
name: this.props.navigation.getParam('name', ''),
|
||||
sidAll: this.props.navigation.getParam('sid', '02FE'),
|
||||
sid2: '',
|
||||
sid3: '',
|
||||
sid4: '',
|
||||
uid: '',
|
||||
sidError: false,
|
||||
|
||||
image: this.props.navigation.getParam('image', ''),
|
||||
index: this.props.navigation.getParam('index', null), // null 이면 카드 새로 생성
|
||||
mode: '',
|
||||
|
||||
update: this.props.navigation.getParam('update', null),
|
||||
|
||||
cardFocused: 'false',
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
let { height, width } = Dimensions.get('window');
|
||||
|
||||
function randomHex4Byte() {
|
||||
let hexString = Math.min(
|
||||
Math.floor(Math.random() * 65536),
|
||||
65535,
|
||||
).toString(16);
|
||||
|
||||
if (hexString.length < 4) {
|
||||
hexString = '0'.repeat(4 - hexString.length) + hexString;
|
||||
}
|
||||
|
||||
return hexString.toUpperCase();
|
||||
}
|
||||
|
||||
if (this.state.index === null) {
|
||||
this.setState(
|
||||
{
|
||||
mode: 'add',
|
||||
sid2: randomHex4Byte(),
|
||||
sid3: randomHex4Byte(),
|
||||
sid4: randomHex4Byte(),
|
||||
cardHeight: ((width - 48) * 53.98) / 85.6,
|
||||
},
|
||||
() => this.updateSID(),
|
||||
);
|
||||
} else {
|
||||
this.setState(
|
||||
{
|
||||
mode: 'edit',
|
||||
sid2: this.state.sidAll.substr(4, 4),
|
||||
sid3: this.state.sidAll.substr(8, 4),
|
||||
sid4: this.state.sidAll.substr(12, 4),
|
||||
cardHeight: ((width - 48) * 53.98) / 85.6,
|
||||
},
|
||||
() => {
|
||||
this.updateSID();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.nameInput.focus();
|
||||
}
|
||||
|
||||
makeSid() {
|
||||
let sid = '02FE' + this.state.sid2 + this.state.sid3 + this.state.sid4;
|
||||
sid = sid.toUpperCase();
|
||||
if (sid.length !== 16) {
|
||||
return '';
|
||||
}
|
||||
if (sid.replace(/[^0-9A-F]/g, '') !== sid) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sid;
|
||||
}
|
||||
|
||||
async updateSID() {
|
||||
if (this.makeSid() !== '') {
|
||||
let uid = await CardConv.convertSID(this.makeSid());
|
||||
this.setState({ uid: uid });
|
||||
}
|
||||
}
|
||||
|
||||
saveCard() {
|
||||
let sid = this.makeSid();
|
||||
|
||||
if (sid === '') {
|
||||
this.setState({ sidError: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.state.update === 'function') {
|
||||
this.state.update(
|
||||
this.state.name,
|
||||
sid,
|
||||
this.state.index,
|
||||
this.state.image,
|
||||
this.props.navigation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setRandomSid() {
|
||||
function randomHex4Byte() {
|
||||
let hexString = Math.min(
|
||||
Math.floor(Math.random() * 65536),
|
||||
65535,
|
||||
).toString(16);
|
||||
|
||||
if (hexString.length < 4) {
|
||||
hexString = '0'.repeat(4 - hexString.length) + hexString;
|
||||
}
|
||||
|
||||
return hexString.toUpperCase();
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
sid2: randomHex4Byte(),
|
||||
sid3: randomHex4Byte(),
|
||||
sid4: randomHex4Byte(),
|
||||
},
|
||||
() => this.updateSID(),
|
||||
);
|
||||
}
|
||||
|
||||
selectPhoto() {
|
||||
ImagePicker.openPicker({
|
||||
cropping: true,
|
||||
width: 856,
|
||||
height: 540,
|
||||
mediaType: 'photo',
|
||||
}).then(res => {
|
||||
if (res && res.path) {
|
||||
this.setState({ image: res.path });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, paddingTop: StatusBar.currentHeight }}>
|
||||
<StatusBar
|
||||
barStyle="dark-content"
|
||||
translucent={true}
|
||||
backgroundColor={'#ffffff'}
|
||||
/>
|
||||
<KeyboardAvoidingView style={{ flex: 1 }}>
|
||||
<View
|
||||
style={{
|
||||
height: 48,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#ffffff',
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 17,
|
||||
fontWeight: 'bold',
|
||||
textAlignVertical: 'center',
|
||||
}}
|
||||
>
|
||||
{this.state.mode === 'add'
|
||||
? i18n.t('header_add')
|
||||
: i18n.t('header_edit')}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
onPress={() => this.saveCard()}
|
||||
>
|
||||
<Icon name="save" size={26} color={'rgba(0,0,0,0.7)'} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView
|
||||
style={{ flex: 1, paddingHorizontal: 24, paddingTop: 16 }}
|
||||
>
|
||||
<ETextInput
|
||||
title={i18n.t('edit_name')}
|
||||
value={this.state.name}
|
||||
onChangeText={text => this.setState({ name: text })}
|
||||
ref={ref => (this.nameInput = ref)}
|
||||
/>
|
||||
<TouchableOpacity onPress={() => this.selectPhoto()}>
|
||||
<ETextInput
|
||||
style={{ marginTop: 24 }}
|
||||
title={i18n.t('edit_background')}
|
||||
value={
|
||||
this.state.image
|
||||
? i18n.t('edit_background_selected')
|
||||
: i18n.t('edit_background_empty')
|
||||
}
|
||||
editable={false}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ marginTop: 24, flexDirection: 'row' }}>
|
||||
<ETextInput
|
||||
title={'SID'}
|
||||
value={'02FE'}
|
||||
onChangeText={text => this.setState({ sid: text })}
|
||||
style={{ flex: 1 }}
|
||||
editable={false}
|
||||
error={this.state.sidError}
|
||||
textStyle={{ textAlign: 'center' }}
|
||||
titleStyle={
|
||||
this.state.sidError !== true &&
|
||||
this.state.sidFocus && { color: '#03A9F4' }
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
width: 20,
|
||||
marginTop: 24,
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
color: '#9E9E9E',
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Text>
|
||||
<ETextInput
|
||||
textStyle={{ textAlign: 'center' }}
|
||||
value={this.state.sid2}
|
||||
onChangeText={text => {
|
||||
this.setState({ sid2: text }, () => this.updateSID());
|
||||
if (text.length === 4) {
|
||||
this.sid3Input.focus();
|
||||
}
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
maxLength={4}
|
||||
error={this.state.sidError}
|
||||
onFocus={() => this.setState({ sidFocus: true })}
|
||||
onBlur={() => this.setState({ sidFocus: false })}
|
||||
autoCapitalize={'characters'}
|
||||
ref={ref => (this.sid2Input = ref)}
|
||||
editable={false}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
width: 20,
|
||||
marginTop: 24,
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
color: '#9E9E9E',
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Text>
|
||||
<ETextInput
|
||||
textStyle={{ textAlign: 'center' }}
|
||||
value={this.state.sid3}
|
||||
onChangeText={text => {
|
||||
this.setState({ sid3: text }, () => this.updateSID());
|
||||
if (text.length === 4) {
|
||||
this.sid4Input.focus();
|
||||
}
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
maxLength={4}
|
||||
error={this.state.sidError}
|
||||
onFocus={() => this.setState({ sidFocus: true })}
|
||||
onBlur={() => this.setState({ sidFocus: false })}
|
||||
autoCapitalize={'characters'}
|
||||
ref={ref => (this.sid3Input = ref)}
|
||||
editable={false}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
width: 20,
|
||||
marginTop: 24,
|
||||
alignSelf: 'center',
|
||||
textAlign: 'center',
|
||||
color: '#9E9E9E',
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Text>
|
||||
<ETextInput
|
||||
textStyle={{ textAlign: 'center' }}
|
||||
value={this.state.sid4}
|
||||
onChangeText={text => {
|
||||
this.setState({ sid4: text }, () => this.updateSID());
|
||||
this.updateSID();
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
error={this.state.sidError}
|
||||
maxLength={4}
|
||||
onFocus={() => this.setState({ sidFocus: true })}
|
||||
onBlur={() => this.setState({ sidFocus: false })}
|
||||
autoCapitalize={'characters'}
|
||||
ref={ref => (this.sid4Input = ref)}
|
||||
editable={false}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 8,
|
||||
fontSize: 14,
|
||||
letterSpacing: -0.4,
|
||||
color: this.state.sidError ? '#F44336' : '#9E9E9E',
|
||||
}}
|
||||
>
|
||||
{i18n.t('edit_sid_notice')}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
marginTop: 24,
|
||||
height: 48,
|
||||
backgroundColor: '#03A9F4',
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
onPress={() => this.setRandomSid()}
|
||||
>
|
||||
<Text
|
||||
style={{ fontSize: 17, color: '#ffffff', fontWeight: '500' }}
|
||||
>
|
||||
{i18n.t('edit_random')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 14,
|
||||
color: '#9E9E9E',
|
||||
marginTop: 20,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
{i18n.t('edit_preview')}
|
||||
</Text>
|
||||
<CardPreview
|
||||
name={this.state.name}
|
||||
uid={this.state.uid}
|
||||
image={this.state.image}
|
||||
cardHeight={this.state.cardHeight ?? 0}
|
||||
/>
|
||||
|
||||
<View style={{ height: 50 }} />
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CardEditScreen;
|
289
src/screens/CardEditScreen.tsx
Normal file
289
src/screens/CardEditScreen.tsx
Normal file
@ -0,0 +1,289 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
KeyboardAvoidingView,
|
||||
TextInput,
|
||||
TextInputProps,
|
||||
ViewStyle,
|
||||
TextInputFocusEventData,
|
||||
NativeSyntheticEvent,
|
||||
TextStyle,
|
||||
} from 'react-native';
|
||||
import CardConv from '../modules/CardConv';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { RootStackParams } from '../../App';
|
||||
import { Shadow } from 'react-native-shadow-2';
|
||||
import CardView from '../components/Card';
|
||||
import { addCard, updateCard } from '../data/cards';
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
import { Card } from '../types';
|
||||
|
||||
type TextFieldProps = TextInputProps & {
|
||||
title: string;
|
||||
containerStyle?: ViewStyle;
|
||||
};
|
||||
|
||||
const generateRandomCardNumber = () => {
|
||||
const getRandom4Byte = () => {
|
||||
return Math.trunc(Math.random() * 65536)
|
||||
.toString(16)
|
||||
.toUpperCase()
|
||||
.padStart(4, '0');
|
||||
};
|
||||
|
||||
return `02FE${getRandom4Byte()}${getRandom4Byte()}${getRandom4Byte()}`;
|
||||
};
|
||||
|
||||
const FieldTitle = (props: { title: string; style?: TextStyle }) => {
|
||||
return (
|
||||
<Text style={[styles.textInputTitle, props.style]}>{props.title}</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const TextField = (props: TextFieldProps) => {
|
||||
const { onFocus, onBlur, title, containerStyle, ...textInputProps } = props;
|
||||
|
||||
const [isFocused, setIsFocused] = useState<boolean>(false);
|
||||
const onFocusCallback = useCallback(
|
||||
(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
||||
setIsFocused(true);
|
||||
onFocus?.(event);
|
||||
},
|
||||
[onFocus],
|
||||
);
|
||||
|
||||
const onBlurCallback = useCallback(
|
||||
(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
||||
setIsFocused(false);
|
||||
onBlur?.(event);
|
||||
},
|
||||
[onBlur],
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<FieldTitle
|
||||
title={title}
|
||||
style={isFocused ? styles.textInputTitleFocused : {}}
|
||||
/>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
onFocus={onFocusCallback}
|
||||
onBlur={onBlurCallback}
|
||||
{...textInputProps}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
styles.textInputBottomBorder,
|
||||
isFocused ? styles.textInputBottomBorderFocused : {},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
type CardAddScreenProps = NativeStackScreenProps<RootStackParams, 'Add'>;
|
||||
type CardEditScreenProps = NativeStackScreenProps<RootStackParams, 'Edit'>;
|
||||
|
||||
const CardEditScreen = (props: CardAddScreenProps | CardEditScreenProps) => {
|
||||
const initialData = props.route.params?.card ?? undefined;
|
||||
|
||||
const [mode] = useState<'add' | 'edit'>(() => {
|
||||
return initialData ? 'edit' : 'add';
|
||||
});
|
||||
|
||||
const [cardName, setCardName] = useState<string>(initialData?.name ?? 'eAM');
|
||||
const [cardNumber, setCardNumber] = useState<string>(() => {
|
||||
return initialData?.sid ?? generateRandomCardNumber();
|
||||
});
|
||||
const uid = useQuery(['uid', cardNumber], () =>
|
||||
CardConv.convertSID(cardNumber),
|
||||
);
|
||||
|
||||
const styledUid = useMemo(() => {
|
||||
if (!uid.isSuccess) {
|
||||
return '카드번호를 불러오는 중...';
|
||||
}
|
||||
|
||||
return (
|
||||
uid.data.match(/[A-Za-z0-9]{4}/g)?.join(' - ') ??
|
||||
'잘못된 카드 번호입니다.'
|
||||
);
|
||||
}, [uid]);
|
||||
|
||||
const onChangeCardName = useCallback((s: string) => {
|
||||
setCardName(s);
|
||||
}, []);
|
||||
|
||||
const changeCardNumber = useCallback(() => {
|
||||
setCardNumber(generateRandomCardNumber());
|
||||
}, []);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const addMutation = useMutation(
|
||||
(card: Card) => {
|
||||
return addCard(card);
|
||||
},
|
||||
{
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries('cards');
|
||||
props.navigation.goBack();
|
||||
},
|
||||
},
|
||||
);
|
||||
const editMutation = useMutation(
|
||||
({ index, card }: { index: number; card: Card }) => {
|
||||
return updateCard(index, card);
|
||||
},
|
||||
{
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries('cards');
|
||||
props.navigation.goBack();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const save = useCallback(() => {
|
||||
const card = {
|
||||
sid: cardNumber,
|
||||
name: cardName,
|
||||
};
|
||||
|
||||
if (mode === 'add') {
|
||||
addMutation.mutate(card);
|
||||
} else {
|
||||
editMutation.mutate({ index: props.route.params!.index, card: card });
|
||||
}
|
||||
}, [
|
||||
addMutation,
|
||||
cardName,
|
||||
cardNumber,
|
||||
editMutation,
|
||||
mode,
|
||||
props.route.params,
|
||||
]);
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView style={styles.screen}>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollView}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<View style={styles.cardPreviewContainer}>
|
||||
<CardView
|
||||
card={{
|
||||
sid: cardNumber,
|
||||
name: cardName,
|
||||
}}
|
||||
mainText={'카드 미리보기'}
|
||||
index={0 /* dummy index */}
|
||||
disabledMainButton={true}
|
||||
hideBottomMenu={true}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={[styles.fieldItemContainer, { paddingTop: 0 }]}>
|
||||
<TextField
|
||||
title={'카드 이름'}
|
||||
value={cardName}
|
||||
onChangeText={onChangeCardName}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.fieldItemContainer}>
|
||||
<TextField title={'카드 번호'} value={styledUid} editable={false} />
|
||||
<Shadow
|
||||
style={styles.buttonShadowStyle}
|
||||
containerStyle={styles.cardNumberChangeButton}
|
||||
distance={4}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[styles.button]}
|
||||
onPress={changeCardNumber}
|
||||
disabled={!uid.isSuccess}
|
||||
>
|
||||
<Text style={styles.buttonText}>카드 번호 변경</Text>
|
||||
</TouchableOpacity>
|
||||
</Shadow>
|
||||
</View>
|
||||
|
||||
<Shadow
|
||||
style={styles.buttonShadowStyle}
|
||||
containerStyle={[
|
||||
styles.cardNumberChangeButton,
|
||||
styles.saveButtonContainerStyle,
|
||||
]}
|
||||
distance={4}
|
||||
>
|
||||
<TouchableOpacity style={[styles.button]} onPress={save}>
|
||||
<Text style={styles.buttonText}>저장</Text>
|
||||
</TouchableOpacity>
|
||||
</Shadow>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollView: {
|
||||
padding: 16,
|
||||
},
|
||||
cardPreviewContainer: {
|
||||
paddingBottom: 32,
|
||||
},
|
||||
fieldItemContainer: {
|
||||
paddingTop: 24,
|
||||
},
|
||||
textInput: {
|
||||
fontSize: 16,
|
||||
paddingTop: 4,
|
||||
},
|
||||
textInputTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
color: '#9e9e9e',
|
||||
},
|
||||
textInputTitleFocused: {
|
||||
color: 'skyblue',
|
||||
},
|
||||
textInputBottomBorder: {
|
||||
paddingTop: 2,
|
||||
backgroundColor: '#9e9e9e',
|
||||
height: 1,
|
||||
},
|
||||
textInputBottomBorderFocused: {
|
||||
backgroundColor: 'skyblue',
|
||||
height: 2,
|
||||
},
|
||||
button: {
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
backgroundColor: 'skyblue',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonShadowStyle: {
|
||||
width: '100%',
|
||||
},
|
||||
saveButtonContainerStyle: {
|
||||
marginTop: 32,
|
||||
},
|
||||
cardNumberChangeButton: {
|
||||
marginTop: 16,
|
||||
},
|
||||
cardImageSelectButton: {
|
||||
marginTop: 8,
|
||||
},
|
||||
buttonText: {
|
||||
color: 'white',
|
||||
},
|
||||
});
|
||||
|
||||
export default CardEditScreen;
|
Loading…
x
Reference in New Issue
Block a user